前端开发
本节介绍电机数据可视化应用案例前端开发流程。
步骤 1:创建工程
在后端工程的同级目录下打开终端,执行命令
npm init vue@latest
,按照下图来勾选选项,构建初始项目。勾选完成后,按照指示命令进入目录,依次执行安装依赖环境。
完成上述步骤后,将得到如下项目目录:
web 工程根目录 ├── node_modules 所有的项目依赖包都放在这个目录下 ├── public 公共文件夹 └── favicon.ico 网站的显示图标 ├── index.html 入口的 html 文件 └── src 源文件目录,编写的代码基本都在这个目录下 ├── assets 放置静态资源的目录 ├── components Vue 的组件文件,自定义的组件都会放到这 ├── router Vue-router Vue 路由的配置文件 ├── store 存放 Pinia 为 Vue 专门开发的状态管理器 ├── views 存放视图文件 ├── App.vue 根组件,这个在 Vue 中也有 └── main.js 入口文件 ├── .eslintrc.js Eslint 配置文件 ├── .gitignore 配置 git 管理 ├── .prettierrc.json prettierrc 配置文件 ├── package.json 命令配置和包管理文件 ├── README.md 项目的说明文件 └── vite.config.js 代理、webpack 打包、插件配置等都会在这里进行
步骤 2:EdgerOS 安全校验
在 utils 文件夹下中创建 auth.js 文件,其负责获取工业边缘计算机前后端通讯的密钥和授权码。
const auth = { token: '', srand: '' }; export function getAuth() { return { token: auth.token, srand: auth.srand }; } export function setHeaders(payload) { auth.srand = payload.srand; auth.token = payload.token; } export function getHeaders() { return { 'edger-token': auth.token, 'edger-srand': auth.srand }; }
在 utils 文件夹下创建 permission.js 文件,其负责向工业边缘计算机申请一系列权限。
import { edger } from '@edgeros/web-sdk'; let permsObtain = {}; export function setPerms(perms) { permsObtain = perms; } export function checkPerms(perms) { const code = []; perms.forEach(perm => { code.push(perm); }); return code; } // all types of permission const permissionArray = [ 'ainn', 'alarm', 'share', 'notify', 'advnwc', 'network', 'display', 'rtsp', 'lora', 'coap', 'wallpaper', 'account', 'printer', 'auxstorage', 'vpn', 'mqtt.publish', 'mqtt.subscribe', 'mediacenter.readable', 'mediacenter.writable', 'mediacenter.removable' ]; // all types of phone const phoneArray = ['camera', 'contacts', 'microphone', 'geolocation', 'browser']; export function requestPerm() { let permission = []; let phone = []; permsNeed.forEach(item => { if (permissionArray.indexOf(item) !== -1) { permission.push(item); } if (phoneArray.indexOf(item) !== -1) { phone.push(item); } }); let configPrem = { code: permission, type: 'permissions' }; let configPhone = { code: phone, type: 'phone' }; // request permission type edger.permission.request(configPrem).then(data => { console.log(`permissionRequest:${JSON.stringify(data)}`); // request phone type edger.permission.request(configPhone).then(data => { console.log(`phoneRequest:${JSON.stringify(data)}`); }); }); }
在 main.js 中,使用 @edgeros/web-sdk 获取前后端通讯需要的 token 和 srand,并使用 auth.js 文件中的 setHeaders 设置它们的值。
import { createApp } from 'vue'; import App from './App.vue'; import { edger } from '@edgeros/web-sdk'; import axios from 'axios'; import { addScoket } from '@/utils/socket.js'; import { setToken, setSrand, getHeaders } from './utils/auth.js'; import { setPerms, checkPerms, requestPerm } from './lib/permission'; // Create Virtual Dom. const app = createApp(App); // Use web-sdk. edger .token() .then(result => { // Get Headers and set. const { token, srand } = result; // Set token and srand setHeaders({ token, srand }); // Create WebSocket. addSocket(token, srand); }) .then(() => { const ret = checkPerms(['network', 'advnwc']); if (ret.length > 0) { requestPerm(); } }) .catch(err => { console.error(err); }) .finally(() => { // Mount Virtual Dom. app.mount('#app'); });
步骤 3:配置 axios
main.js 文件中,添加
axios
的请求头设置,并在请求头中附加校验信息。import axios from 'axios'; axios.interceptors.request.use(config => { // Get headers and set axios config.headers = getHeaders(); return config; });
使用 Vue 存储库 Pinia 管理需要全局访问的变量。在 stores 文件夹中新建 store.js 文件,并配置需要的全局变量 : resData 及 isClicked。
import { ref, computed } from 'vue'; import { defineStore } from 'pinia'; export const useStore = defineStore('counter', () => { const resData = ref({ cycleData: [] }); const isClicked = ref(false); return { resData, isClicked }; });
步骤 4:封装图表组件
在 components 文件夹下新建 SpeedChart.vue 文件,根据自身需求配置好 Echart 图表参数。此处设计了一张示例图表。
const option = { xAxis: { type: 'time', splitNumber: 4 }, yAxis: { type: 'value', max: 60, min: -60, splitLine: { show: false } }, grid: { top: 30, left: 35, right: 5, bottom: 24 }, series: [] };
完善创建 Echarts 图表代码。
<template> <div id="chart_panel"> <div class="title"> <img src="@/assets/tips.svg" /> <div class="text">电机转速监控模块</div> <div class="scale">(单位:rad/s)</div> </div> <div class="decorate"></div> <div id="chart-board"></div> </div> </template> <script setup> import * as echarts from 'echarts' import { useCounterStore } from '@/stores/counter' import { onMounted } from 'vue' var store = useCounterStore() var chart = null const buildChart = () => { chart = echarts.init(document.getElementById('chart-board')) // Use Option Config const option = option chart.setOption(option) } var series = [] const updateChart = () => { const item = { type: 'line', smooth: true, sampling: 'average', areaStyle: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [ { offset: 0, color: 'rgba(3,169,244,0.8)' }, { offset: 1, color: 'rgba(58,77,233,0.3)' } ]) }, data: JSON.parse(JSON.stringify(store.resData.cycleData)) } series = [] series.push(item) chart.setOption({ series }) setTimeout(() => { chart.resize() }, 200) } onMounted(() => { buildChart() }) store.$subscribe(() => { if (store.resData.cycleData.length > 10) { store.resData.cycleData.shift() } updateChart() }) </script> <style scoped lang="scss"> #chart_panel { width: 453px; height: 337px; } </style>
步骤 5:封装控制组件
在 components 文件夹下新建 ControlPanel.vue 文件用于编写控制界面。
<template> <div id="control_panel"> <div class="title"> <img src="@/assets/tips.svg" /> <div class="text">设备启停控制模块</div> </div> <div :class="['control_btn', { 'control_btn-active': store.isClicked }, btnClickStyle]" @click="controlMotor()" @mousedown="mouseDown()" @mouseup="mouseDown()" > <div class="btn_color"></div> <img src="@/assets/button.png" style="width: 100%; height: 100%" /> </div> </div> </template> <script setup lang="ts"> import { ref } from 'vue' import axios from 'axios' import { useCounterStore } from '@/stores/counter' const store = useCounterStore() const btnClickStyle = ref({ 'control_btn-click': false }) const mouseDown = () => { btnClickStyle.value['control_btn-click'] = !btnClickStyle.value['control_btn-click'] } // 改变电机运行状态的请求函数 const controlMotor = async () => { store.isClicked = !store.isClicked let res = await axios.post('/state', { run: store.isClicked }) console.log(res) } </script>
步骤 6:编写主页内容
在 views 文件夹下新建 HomeView.vue 文件用于编写主页内容,并将封装好的两个组件引入其中。
<template> <div class="content"> <div class="left"> <SpeedChart></SpeedChart> </div> <div class="right"> <ProductCycle></ProductCycle> </div> </div> </template> <script setup lang="ts"> import { useStore } from '@/stores/store.js' import SpeedChart from './components/SpeedChart.vue' import ProductCycle from './components/ControlPanel.vue' import axios from 'axios' const store = useStore() // 每秒获取电机转速的请求函数 setInterval(async () => { let resData = await axios.get('/state') console.log(resData) if (resData.data) { // 请求成功 if (resData.data.time && resData.data.data.rotateSpeed) { store.resData.cycleData.push([resData.data.time, resData.data.data.rotateSpeed]) } } else { console.error('connect error :', resData.data.message) } }, 1000) </script> <style scoped lang="scss"> @import '@/assets/style/base.scss'; // 模块 .content { display: grid; grid-template-columns: 1fr 1fr; margin: 0 auto; font-weight: normal; box-sizing: border-box; .left, .right { margin-top: vh(192); } .left { margin-right: 0px; } .right { margin-left: 37px; } } </style>
步骤 7:前端全览工程
说明:
如果直接使用全览工程,需要在当前目录打开终端,输入
npm install
安装依赖。
完整前端工程结构及代码请参考 visual_develop_web 工程示例。