网页端使用主流的vue进行搭建。在element和iview之间选择了iview这个前端框架。从个人角度element对开发者更加友好一些,很多内置的方法、组件都可以自定义实现,而且element社区要比iview活跃的多。那么为什么最终选择了iview呢,因为iview的界面ui比element相对丝滑一些。比较符合我的审美。主要原因还是因为懒惰。去年我已经用iview开发过了一套系统。所以这次就直接复用了之前的用户、权限、机构、角色这些大模块。减少了一部分工作量。当然后续如果有机会肯定是优先选择用element翻新一下。其实从本质上,无论是element还是iview区别并不大,他们都是vue的生态圈相对火热的框架,所以无论选择哪一个都可以。这边就先用iview示范如何搭建PC端。
一、框架搭建
1.安装环境
首先下载node环境,安装nodejshttp://nodejs.cn/
然后打开cmd,输入node -v、npm -v确认node环境是否安装成功
然后配置淘宝镜像源
1. npm config set registry https://registry.npm.taobao.org 2. npm config get registry
2.获取源码
从git上拉取https://github.com/iview/iview-admin.git
然后执行npm install、npm run dev
就可以看到iview基本展示页面了
iview-admin已经自带了基本的各种组件,可以很大程度上节省我们时间。缺点也很明显就是引用了过多的冗余组件,致使加载速度降低。当然这些我们都可以自行优化。
3.开发前导
在进行iview开发之前,我们要尽量对iview有足够的了解。建议多看看官网https://www.iviewui.com/docs/introduce
基本上都是即粘即用。大部分的组件都是iview已经为我们写好了,极个别没有的,我们也都可以用简单组件自己实现,所以尽量不是我们去创造组件,而是合理运用已经存在的组件,多去官网查找,复制粘贴才是无望而不利的神器。
二、基础开发
1.去除Eslint校验
iview-admin是内置了eslint的,这种校验对于一开始写代码是不太方便的,当然如果一直开着也可以规范我们的代码风格,我个人比较不喜欢这种约束。一般我都是把它关闭了,最后项目成型后,使用
npm run lint-fix
进行代码修复。在view中只需要在根目录的.eslintignore文件中写上*,就会自动忽略所有格式问题。
2.配置后台服务代理
一般服务都是要进行访问真实的后台接口,我们在使用iview后就尽量前后端分离部署。前台使用nginx部署,后台jar包方式部署。这样前台服务访问后台就需要代理进行搭桥。
在项目根目录的vue.config.js中可以找到devServer的属性,这个就是配置代理的地方
devServer: { host: 'localhost', port: 8010, proxy: { '/netgate-server': { // target: 'http://localhost:8001/', target: 'http://127.0.0.1:8001/', //pathRewrite: {'^/netgate-server':''}, changeOrigin: true } } }
这样当我前台使用netgate-server这个属性的 时候就会自动跳转到http://127.0.0.1:8001/这个后台地址
三、核心组件
1.MQTT通讯组件
etcloud采用的是emq作为broker,前台服务作为一个client,后台服务也是一个client,微信端同样是一个client。通过emq中间件进行消息的流通传递。
首先我们需要引入npm的mqtt模块
npm install mqtt@2.0.0
这里有个小坑,最新的mqtt模块和vue不兼容。因为ts的问题。我也没有过多研究,就直接降低了mqtt的版本。
页面上如何使用呢
<template> <Row> <Card> <p slot="title"> <Icon type="android-person"></Icon> 设备客户端测试 </p> <div> <Row> <i-col span="8"> <Select style="width:200px" placeholder="请选择设备"> <Option v-for="(item,index) in deviceList" :value="item.sn" :key="index" @click.native="choseDevice(item)">{{ item.sn }}</Option> </Select> <Button type="success" @click="connectMq">连接</Button> <Button type="error" @click="disConnectMq">断开</Button> </i-col> <i-col span="16"> <Row> <i-col span="12"> 产品ID:{{curDevice.pid}} </i-col> <i-col span="12"> 产品TOKEN:{{curDevice.token}} </i-col> </Row> <Row> <i-col span="12"> 设备序列号:{{curDevice.sn}} </i-col> <i-col span="12"> 产品名称:{{curDevice.pname}} </i-col> </Row> </i-col> </Row> </div> </Card> </template> <script> const uuidv1 = require('uuid/v1'); import mqtt from 'mqtt' const moment = require('moment'); // 连接选项 let options = { clean: true, // 保留回话 connectTimeout: 4000, // 超时时间 // 认证信息 clientId: 'test_client_'+Math.random(), username: 'test', password: 'test', } const connectUrl = 'wss://www.etcloud.club:8084/mqtt' export default { name: 'mqtt-test', data() { return { client:{},//mqtt客户端 } }, created(){ //页面刚进入时开启长连接 //this.getDeviceList(); }, destroyed: function() { //页面销毁时关闭长连接 this.client.end(); this.$Message.success('断开成功'); }, methods: { connectMq(){ if(JSON.stringify(this.curDevice) == "{}"){ this.$Message.info('请先选择设备'); return; } if(this.client.connected){ return } options.clientId = this.curDevice.sn; options.username = this.curDevice.pid; options.password = this.curDevice.token; this.client = mqtt.connect(connectUrl, options) console.log(this.client) this.client.on('connect', (e) => { this.$Message.success('连接成功'); }) // this.client.subscribe('/World1234', { qos: 1 }, (error) => { // if (!error) { // console.log('订阅成功') // } else { // console.log('订阅失败') // } // }) // 接收消息处理 this.client.on('message', (topic, message) => { console.log('收到来自', topic, '的消息', message.toString()) var msgObj = {}; new Promise((resolve, reject) => {/* executor函数 */ msgObj = JSON.parse(message.toString()); resolve(msgObj); }).catch(function (value) { console.log('JSON转化异常') return; }); this.sdData.push({time:moment().format("YYYY-MM-DD HH:mm:ss"),topic:topic,content:msgObj}) }) // 断开发起重连 this.client.on('reconnect', (error) => { console.log('正在重连:', error) }) // 链接异常处理 this.client.on('error', (error) => { console.log('连接失败:', error) }) }, disConnectMq(){ this.sbData = []; this.sdData = []; this.curMsg={ topic:'', obj:{} }, this.subTopic={}; this.subTopicList=[]; if(this.client.connected){ this.client.end(); } // this.curMsg.topic = item.pid+'/'+item.sn+'/client' // this.subTopic.topic = item.pid+'/'+item.sn+'/server' // this.subTopic.qos = 0 this.$Message.success('断开成功'); }, //订阅主题 subTopicHandle(){ console.log(this.subTopic) console.log(this.subTopic.qos) console.log(this.subTopic.qos=='') if(JSON.stringify(this.curDevice) == "{}"){ this.$Message.info('请先选择设备'); return; }else if(!this.client.connected){ this.$Message.info('请先选连接设备'); return; }else if(this.subTopic.topic==''){ this.$Message.info('订阅主题不能为空'); return; } for(let i=0;i<this.subTopicList.length;i++){ if(this.subTopic.topic == this.subTopicList[i].topic){ this.$Message.info('相同的主题无法订阅两次'); return; } } // else if(this.subTopic.qos==''){ // this.$Message.info('订阅消息质量不能为空'); // return; // } this.client.subscribe(this.subTopic.topic, { qos: this.subTopic.qos }, (error) => { if (!error) { this.$Message.success('订阅成功'); this.subTopicList.push({time:moment().format("YYYY-MM-DD HH:mm:ss"),topic:this.subTopic.topic,qos:this.subTopic.qos}) } else { this.$Message.error('订阅失败'); } }) }, }, } </script>
2.JsonEditor
可以格式化数据为json格式,因为系统中所有的消息都是采用json格式进行通讯的,所以引入这个组件可以有效的减少json出错率,十分好用
首先我们先引入JsonEditor
npm install jsoneditor --save
接下来我们把JsonEditor封装成一个组件
<!--codemirror-json格式化--> <template> <div class="json-editor"> <textarea ref="textarea"/> </div> </template> <script> import CodeMirror from 'codemirror' import 'codemirror/addon/lint/lint.css' import 'codemirror/lib/codemirror.css' import 'codemirror/theme/rubyblue.css' // require('script-loader!jsonlint') import 'codemirror/mode/javascript/javascript' // import 'codemirror/addon/lint/lint' import 'codemirror/addon/lint/json-lint' export default { name: 'JsonEditor', /* eslint-disable vue/require-prop-types */ props: ['value'], data () { return { jsonEditor: false } }, watch: { value (value) { const editor_value = this.jsonEditor.getValue() if (value !== editor_value) { this.jsonEditor.setValue(JSON.stringify(this.value, null, 2)) } } }, mounted () { this.jsonEditor = CodeMirror.fromTextArea(this.$refs.textarea, { lineNumbers: true, mode: 'application/json', gutters: ['CodeMirror-lint-markers'], theme: 'rubyblue', autoRefresh: true, lint: true }) this.jsonEditor.setValue(JSON.stringify(this.value, null, 2)) this.jsonEditor.on('change', cm => { this.$emit('changed', cm.getValue()) this.$emit('input', cm.getValue()) }) }, methods: { getValue () { return this.jsonEditor.getValue() }, refresh() { this.jsonEditor && this.jsonEditor.refresh(); } } } </script> <style scoped> .json-editor { height: 100%; position: relative; } .json-editor >>> .CodeMirror { height: auto; min-height: 180px; } .json-editor >>> .CodeMirror-scroll { min-height: 180px; } .json-editor >>> .cm-s-rubyblue span.cm-string { color: #f08047; } </style>
在页面上调用组件,传入参数即可
import JsonEditor from '@/views/main-components/CodeEditor'
记得声明组件
components: { JsonEditor },
在页面中调用组件,然后对curMsg.obj传参即可使用
<json-editor style="width: 98%" ref="jsonEditor" v-model="curMsg.obj"/>
效果图
3.粘贴板组件Clipboard
Clipboard可以实现点击复制某段文字,在我们输入密钥、各种ID的时候十分方便,点击即可复制某个字符串。
安装
npm install clipboard --save
引入
import Clipboard from 'clipboard';
页面中使用
<Tooltip placement="top"> <span style="display:block;cursor:pointer;width:100%;overflow: hidden;text-overflow:ellipsis;white-space: nowrap;height:12px;line-height: 15px;" class="tag-read2" :data-clipboard-text="productInfo.token" @click="copy(2)">{{productInfo.token}}</span> <div slot="content"> 点击复制 </div> </Tooltip>
方法
copy(type) { var clipboard = new Clipboard('.tag-read'+type) clipboard.on('success', e => { this.$Message.success('复制成功'); // 释放内存 clipboard.destroy() }) clipboard.on('error', e => { // 不支持复制 释放内存 this.$Message.error('不支持复制'); clipboard.destroy() }) },
效果图
4.设备树
设备树可以直观的个人所拥有的产品、设备的概况、在线情况、连接日志、指令日志、功能日志……设备树是整个系统的门面和入口,当然后续我也打算做一些Echarts的报表图。
首先在vue的data中声明
gatewaydata: [ { title: '设备列表', expand: true, render: (h, { root, node, data }) => { return h('span', { style: { display: 'inline-block', width: '100%' } }, [ h('span', [ h('Icon', { props: { type: 'network', color: '#2d8cf0', size: '15' }, style: { marginRight: '8px' } }), h('span', data.title) ]), ]); }, children: [] } ], buttonProps: { type: 'ghost', size: 'small', },
然后再页面中写入树
<Tree :data="gatewaydata" :render="renderContent" style="overflow-y: auto;overflow-x: hidden " :style="treestyle"></Tree>
然后在methods中写明render对树进行渲染
renderContent (h, { root, node, data }) { return h('span', { style: { display: 'inline-block', width: '100%' } }, [ h('span', [ h('Icon', { props: { type: 'record', size: '12', color: data.color }, style: { marginRight: '8px' } }), h('Button', { props: Object.assign({}, this.buttonProps, { type: data.buttontype, inner: data.title }), style: { margin: '-4px 0px 0px 0px' }, on: { click: () => { this.handleTreeClick(data) }, hover: () => { //console.log(11) } } }, data.title) ]) ]); },
对于树节点的设备的上线下线只需要更改data中gateawaydata子元素的color即可
if(topic.startsWith('$SYS/brokers/')){ let status = topicArr[5] if(status=='connected'){ for(let j=0;j<self.gatewaydata[0].children.length;j++){ if(self.gatewaydata[0].children[j].id==msgObj.username){ for(let k=0;k<self.gatewaydata[0].children[j].children.length;k++){ if(self.gatewaydata[0].children[j].children[k].sn==msgObj.clientid){ self.gatewaydata[0].children[j].children[k].color = '#19be6b'; return; } } } } }else if(status=='disconnected'){ for(let j=0;j<self.gatewaydata[0].children.length;j++){ if(self.gatewaydata[0].children[j].id==msgObj.username){ for(let k=0;k<self.gatewaydata[0].children[j].children.length;k++){ if(self.gatewaydata[0].children[j].children[k].sn==msgObj.clientid){ self.gatewaydata[0].children[j].children[k].color = '#bbbec4'; return; } } } } } }
四、总结
其实引用的组件远不止这些,vue还有各种很好玩的组件只要往往给人眼前一亮的感觉,所以要勇于尝试,不过结局如何,过程也会其乐无穷。PC端其实我并没有引用很复杂的东西,大部分功能都是在iview的组件基础上完成的,比如树形结构、比如扩展列、父子组件的传参监听、权限控制路由显示……。但是总体上PC端并不难,这篇文章更多的是一个引子,你可以用不同的方式去实现更酷的界面,更好用的功能。前端的路很精彩!