前端开发

更新时间:
2024-04-26
下载文档

前端开发

本节介绍远程 IO 输出控制应用案例前端开发流程。

步骤 1:创建工程

  1. 在后端工程的同级目录下打开终端,执行命令 npm init vue@latest 。按照下图勾选选项,构建初始项目。

    img

  2. 勾选完成后,按照指示命令进入目录,依次执行安装依赖环境。

    img

  3. 完成上述步骤后,将得到如下项目目录:

    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 安全校验

  1. utils 文件夹下中创建 auth.js 文件,其负责获取工业边缘计算机前后端通讯的密钥和授权码。

    const auth = {
      token: '',
      srand: ''
    };
    
    export function getAuth() {
      return { token: auth.token, srand: auth.srand };
    }
    
    export function setToken(payload) {
      auth.token = payload;
    }
    
    export function setSrand(payload) {
      auth.srand = payload;
    }
    
    export function getHeaders() {
      return {
        'edger-token': auth.token,
        'edger-srand': auth.srand
      };
    }
    
  2. 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)}`);
        });
      });
    }
    
  3. main.js 中,使用 @edgeros/web-sdk 获取前后端通讯需要的 token 和 srand。

    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;
        // Reserve 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:配置 WebSocket

  1. utils 文件夹中创建 socket.js,其负责创建 WebScoket 通讯客户端。

    import io from 'socket.io-client';
    
    export var socket = null;
    // socket IO
    export const addSocket = (token, srand) => {
      socket = io({
        query: {
          'edger-token': token,
          'edger-srand': srand
        },
        transports: ['websocket']
      });
    };
    // Socket handler add
    export const addScoketHandler = (url, cb) => {
      socket.on(url, cb);
    };
    
  2. 使用 Vue 存储库 Pinia 管理需要全局访问的变量,在 stores 文件夹中新建 store.js 文件,配置全局变量 IOState。

    import { reactive } from 'vue';
    import { defineStore } from 'pinia';
    export const useStore = defineStore('dataStore', () => {
      const IOState = reactive({ input: [], output: [] });
    
      return {
        IOState
      };
    });
    
  3. 根据接口定义,在 App.vue 中的 script 标签中编写 IO 状态改变时 /state 的 WebSocket 回调函数。

    import { reactive } from 'vue';
    import { addScoketHandler } from '@/utils/socket.js';
    import { useStore } from '@/stores/store.js';
    
    const store = useStore();
    
    // Callback : refresh IO state
    addScoketHandler('/state', resData => {
      if (resData.input && resData.output && resData.input.length > 0 && resData.output.length > 0) {
        store.IOState.input = resData.input;
        store.IOState.output = resData.output;
      }
    });
    

步骤 4:配置 HTTP 接口

  1. main.js 中添加 axios 的请求头设置,在请求头中附加校验信息。

    import axios from 'axios';
    
    axios.interceptors.request.use(config => {
      // Set Headers
      config.headers = getHeaders();
      return config;
    });
    
  2. 根据接口定义,编写请求函数,下面是一段示例。

    import axios from 'axios';
    
    const changeIO = async (ip, position, value) => {
      await axios.post('/io/state', { ip, IOId: '1', position, value });
    };
    

步骤 5:IO 数据可视化组件封装

  1. assets 文件夹中新建 base.scss 文件,在其中进行 Scss 计算函数封装。

    @use 'sass:math';
    $page_width: 375;
    $page_height: 812;
    
    @function vw($px) {
      @return math.div($px, $page_width) * 100vw;
    }
    
    @function vh($px) {
      @return math.div($px, $page_height) * 100vh;
    }
    
  2. components 文件夹中新建 IO.vue 文件编写 IO 状态显示组件。

    <template>
      <div class="IOBar">
        <div :class="['IONodeBar', { 'IONodeBar--unactive': props.IOType === 'input' }]">
          <div class="loading" v-for="(item, index) in store.IOState[props.IOType]" :key="index">
            <div class="IONode">
              <transition name="v" mode="out-in">
                <div class="loading--active" v-if="props.IOType === 'output' && item.isChanging">
                  <div class="icon">
                    <span class="van-loading__spinner van-loading__spinner--circular img">
                      <svg class="van-loading__circular" viewBox="25 25 50 50">
                        <circle cx="50" cy="50" r="20" fill="none"></circle>
                      </svg>
                    </span>
                  </div>
                </div>
              </transition>
    
              <div :class="['nodePanel']" @click="changeIO(item.position, item.value)">
                <div class="nodeText">{{ item.position }}</div>
                <div
                  :class="[
                    { 'node--unactive': !item.value },
                    {
                      'node--loading': props.IOType === 'output' && item.isChanging
                    },
                    'node'
                  ]"
                ></div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </template>
    
    <script setup>
    import { ref, watch } from 'vue'
    import { useStore } from '@/stores/store.js'
    import axios from 'axios'
    
    const store = useStore()
    const props = defineProps({
      IOType: String,
      ip: String,
      IOId: String
    })
    
    const changeIO = async (position, value) => {
      store.IOState[props.IOType].forEach((ele) => {
        if (ele.position === position) {
          ele.isChanging = true
        }
      })
      await axios.post('/io/state', { ip: props.ip, IOId:'1', position, value: !value })
    }
    </script>
    
    <style scoped lang="scss">
    /* import base.scss */
    @import '@/assets/base.scss';
    
    .IOBar {
      display: flex;
      width: vw(304);
      height: vh(100);
      margin-top: vh(14);
      margin-left: vw(17);
      padding: 0;
      justify-content: center;
    
      .IONodeBar {
        display: flex;
        justify-content: space-between;
        flex-wrap: wrap;
        width: vw(304-25);
        height: 100%;
        background: rgba(255, 255, 255, 0.85);
    
        .loading {
          flex: 12.5%;
          &:nth-child(n + 9) {
            margin-top: 10px;
          }
    
          .IONode {
            position: relative;
            .loading--active {
              position: absolute;
              width: 100%;
              height: 100%;
              border-radius: 15px;
              z-index: 1;
    
              .icon {
                display: flex;
                flex-direction: column;
                justify-content: center;
                width: 22px;
                height: 22px;
                margin: 0 auto;
                margin-top: 23px;
                border-radius: 50%;
    
                .img {
                  width: 12px;
                  height: 12px;
                  margin: 0 auto;
                  color: white;
                  animation: spaning 1s linear infinite;
                }
              }
            }
            .nodePanel {
              display: flex;
              flex-direction: column;
              justify-content: center;
              width: 21px;
              height: 44px;
              margin: 0 auto;
              background: #edf4ed;
              border-radius: 15px;
              .nodeText {
                text-align: center;
              }
              .node {
                width: 21px;
                height: 21px;
                background: linear-gradient(180deg, #69c969 0%, #298000 100%);
                border-radius: 100%;
              	.&--unactive {
                	background: linear-gradient(180deg, #e4e7e4 0%, #828580 100%);
              	}
              	.&--loading {
                	opacity: 0.45;
              	}
              }
            }
          }
        }
      	.&--unactive {
        	opacity: 0.45;
      	}
      }
    }
    @keyframes spaning {
      0% {
        transform: rotate(0deg);
      }
    
      100% {
        transform: rotate(360deg);
      }
    }
    </style>
    

步骤 6:IO 控制界面编写

App.vue 中的各标签按如下内容替换:

<template>
  <div class="detialNavBar">
     <div class="content">
      <div class="IOtab input">
        <div class="title">
          输入状态
          <span class="statement"> (只读) </span>
        </div>
        <IO :IOType="type[0]" :ip="127.0.0.1" :IOId="IOId"></IO>
      </div>

      <div class="IOtab">
        <div class="title">
          输出状态
          <span class="statement"> (点击可更改) </span>
        </div>
        <IO :IOType="type[1]" :ip="127.0.0.1" :IOId="IOId"></IO>
      </div>
    </div>
  </div>
</template>

<script setup>
import IO from '@/components/IO.vue'
import { edger } from '@edgeros/web-sdk'
import { computed, ref } from 'vue'
import { reactive } from 'vue'
import { addScoketHandler } from '@/utils/socket.js'
import { useStore } from '@/stores/store.js'

const store = useStore()
// Callback : refresh IO state
addScoketHandler('/state', (resData) => {
    if(resData.input && resData.output && resData.input.length > 0 && resData.output.length > 0){
        store.IOState.input = resData.input
        store.IOState.output = resData.output
    }
})

const type = ['input', 'output']
</script>

<style lang="scss">
@import '@/assets/base.scss';
.detialNavBar {
  height: 100vh
  width: 100%;
  overflow: hidden;
  .content {
    margin-top: vh(32);
    .IOtab {
      width: vw(338);
      height: vh(166);
      margin: 0 auto;
      margin-top: vh(16);
      border-radius: 6px;
      background: linear-gradient(180deg, #effffe -11.75%, #ffffff 31.63%);
      .title {
        padding-top: vh(13);
        margin-left: vw(16);
        font-style: normal;
        font-weight: 400;
        font-size: 16px;
        line-height: 19px;
        letter-spacing: -0.3px;
        color: #000000;
        .statement {
          font-size: 14px;
          color: #429d29;
        }
      }
    }
  }
}
</style>

步骤 7:前端全览工程

说明:

如果直接使用全览工程,需要在当前目录打开终端,输入 npm install 安装依赖。

完整前端工程结构及代码请参考 io_develop_web 工程示例open in new window

文档内容是否对您有所帮助?
有帮助
没帮助