Skip to main content
Web端开发指南

引言#

智汀家庭云Web版 是一款结合智慧中心(SA)的Web客户端,支持用户在SA所在局域网环境下实现智能家居的设备控制等功能。

1. 开发前准备#

项目运行环境

要运行智汀家庭云,你必须要在本地搭建好以下环境

  1. 安装nodejs的开发环境(node安装参考教程
  2. 下载一个集成开发工具,一般这里推荐vscode,直接去官网下载安装即可

技能准备

以下是主要涉及的技术,为了你能顺利的进行开发,你必须先熟悉他们

项目获取

当你的环境安装好了,还有所需的技能已经掌握,那么现在你可以从源码开源网站获取我们的项目。

  1. Git Hub

    名称URL描述
    smartassistant-vuehttps://github.com/zhiting-tech/smartassistant-vueWeb源码
  2. gitee

    名称URL描述
    smartassistant-vuehttps://gitee.net/zhiting-tech/smartassistant-vueWeb源码

安装依赖

项目获取成功后,直接用vscode打开项目

如图:

然后用快捷键 “ctrl + ~” 打开命令窗口

然后我们可以在窗口输入以下命令安装项目所需的依赖

npm installyarn install

如图:

如果你不是用vscode打开

那么你可以用你操作系统的命令窗口进入到该项目根目录

执行上面命令一样可以安装项目依赖

如图:

启动项目

等你项目依赖安装完后,你可以执行以下命令启动项目

npm run serve

如图:

2. 快速开始#

2.1 开发工具#

2.2 源码地址#

  1. Git Hub

    名称URL描述
    smartassistant-vuehttps://github.com/zhiting-tech/smartassistant-vueWeb源码
  2. gitee

    名称URL描述
    smartassistant-vuehttps://gitee.net/zhiting-tech/smartassistant-vueWeb源码

2.3 构建版本#

  • 首先我们通过git远程拉取项目
  • 项目拉取成功后,进入到项目的根目录,安装项目依赖
  • 安装好了依赖,我们就可以启动项目了
现在项目已经可以跑起来,并且可以在浏览器上看到我们的项目,但还只是停留在登录页面

那现在怎么让我们的项目整个流程走通,我们需要启动我们的后端项目

依赖后端Golang项目

使用 Docker 运行智汀家庭云

Golang开发环境搭建

有了后端接口,我们如何能让我们的项目调用到我们自己的接口?

修改我们接口的请求地址

在我们项目的根目录下找到vue.config.js这个配置文件,打开我们的vue.config.js

我们只需要把tartget改成接口的地址,我们就可以调用接口的数据了

更多配置请查看传送门

新增账号

为了使我们更流畅的体验项目,我们需要一个测试账号

一般在本地新增账号有两种方式:

  1. 手动往数据库里添加一个
  2. 下载智汀app,连接sa后设置一个账号密码

体验专业版

账号登录成功后,进入智慧家庭云,体验Web端专业版

3. 开发指南#

3.1 项目架构#

这里主要讲下项目的结构和技术架构,方便我们快速开始开发

3.1.1 项目技术架构#

  • 本项目主要采用vue + webpack + vant
  • 项目主要用vue-cli3脚手架进行初始化,然后根据项目需求进行结构上的调整

vuejs特性

  • vuejs体积小
  • 学习成本低
  • 能快速提交开发效率
  • vuejs的生态完善

webpack特性

  • 模块化
  • 按需加载
  • 丰富的插件
  • 丰富的配置

vant特性

  • 提供 60 多个高质量组件,覆盖移动端各类场景
  • 性能极佳,组件平均体积不到 1kb(min+gzip)
  • 单元测试覆盖率 90%+,提供稳定性保障
  • 完善的中英文文档和示例
  • 支持 Vue 2 & Vue 3
  • 支持按需引入
  • 支持主题定制
  • 支持国际化
  • 支持 TypeScript
  • 支持 SSR

3.1.2 项目结构#

│  .browserslistrc │  .editorconfig│  .eslintrc.js          // eslint配置文件│  .gitignore            // git提交忽略配置│  babel.config.js       // babel配置文件│  package-lock.json     // 依赖缓存文件│  package.json          // package配置文件│  postcss.config.js     // postcss独立配置文件│  README.md             // 说明文件│  vue.config.js         // webpack相关配置文件├──public                // 静态资源文件├──plugins               // 插件集合└─src    ├─api    │   ├──http.js       // api请求文件,每个api请求都在这里    │   ├──instance.js   // api请求总入口,在这里可以做请求的统一处理    ├─assets             // 资源文件夹    ├─lang               // 多语言文件夹    ├─bus                // 全局vue bus    ├─components         // 通用组件库    ├─router.js          // 路由文件    ├─store.js           // store相关    ├─App.vue           // 程序入口vue文件    ├─main.js           // 程序入口文件    ├─utils              // 相关工具的方法集合    └─views              // 页面文件

3.1.3 示例:hello word#

怎样添加一个hello word的页面?

  • 步骤1: 在views文件夹新建一个hello-world的文件夹,然后在hello-world文件夹下新建一个index.vue的文件,index.vue的文件内容为
// 模板模块<template><div>hello word</div></template>// 逻辑模块<script></script>// 样式模块<style scoped></styple>
  • 步骤2: 建完页面文件后,我们需要在router.js路由文件配置hello-world页面的路由
// 引入页面文件import HelloWorld from './views/hello-world/index.vue'
// 配置路由文件export default new Router({  mode: 'hash',  base: process.env.BASE_URL,  routes: [    {      path: '/hello-world',      name: 'device',      component: HelloWorld    }  ]})
  • 步骤3:我想在hello-world页面上添加一张图片怎么办?我们可以把我们的图片资源丢到assets目录下,然后用相对路径引入就可以了
    例:要引入名为hello.png的图片
<template>    <div>        <img src=".././img/hello.png" />        <p>hello word</p>    </div></template>
  • 步骤4:我要用公用组件库的组件怎么办?我们可以在components目录下引入要使用的组件
    例:
// 模板模块<template>    <div>        <HelloComponent/>        <img src=".././img/hello.png" />        <p>hello word</p>    </div></template>// 引入组件<script>import HelloComponent from '@/components/HelloComponent.vue'export default {    // 在页面注册组件    compoents: {        HelloComponent    }}</script>
  • 步骤5:如果我们需要接口数据,我们可以在api文件夹下的http.js增加我们的接口
/*** 请求列子*/export const example = params => http.g(  `${apiHeader}/examle`,  params)
  • 步骤6:在浏览器输入<localhost:8080/#/hello-world>就可以看到我们的hello页面了

3.2 功能结构#

  智汀智能生态,从功能结构上来说,包括家庭/公司、房间/区域管理,成员、角色权限管理,插件管理、设备管理以及设备场景等。

如下图:

图. 智慧中心功能结构图

其中,对于Web端专业版,核心在于设备的通讯、发现、控制,用户角色权限控制以及插件包的开发,接下来我们将详细进行介绍。

3.3 业务功能:设备通讯#

  对于智汀家庭云Web版而已,与设备的通讯都离不开智慧中心的通讯。在这里,我们采用Websocket技术,以Websocket建立长链接进行通讯信息接收传输。

  • Websocket 介绍

    Websocket是一种在单个TCP连接上进行全双工通信的协议,Websocket API也被W3C定为标准,Websocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。

    Websocket的构造函数、常量、属性、方法,事件 ->Websocket

本应用已封装Websocket方法"ws-plugin"上传npm 官网,可在官网搜索 “ws-plugin”查看调用方法

  • ws-plugin” 的安装
npm i ws-plugin --save
  • ws-plugin” 的页面引入
import Socket from 'ws-plugin'const ws = new Socket({  url: null, // 链接的通道的地址  heartTime: 60 * 1000 * 3, // 心跳时间间隔  heartMsg: 'ping', // 心跳信息,默认为'ping'  isRestroy: false, // 是否销毁  reconnectCount: 10, // 自动重连次数,-1为无限次重连  reconnectTime: 5000, // 自动重连时间间隔,单位毫秒,默认5秒  onOpen: null, // 连接成功的回调  onClose: null, // 关闭的回调  onMessage: null, // 消息的回调  onError: null // 错误的回调})
// 方法调用// 已建立通讯ws.onOpen(() => {  // do something here})
// 接收通讯信息ws.onMessage((data) => {    // do something here})
  • 相关调用方法:
方法名称方法说明参数
onOpen连接成功的回调函数传一个回调函数
onClose关闭 ws 连接传一个回调函数
onMessage接受信息回调传一个回调函数
onError连接错误回调传一个回调函数
reconnectTime重连时间默认值 5 * 1000(单位:毫秒)

完成通讯,接下来我就可以获取我们需要的信息对设备进行一些操作了,很nice!!

3.4 业务功能:设备发现#

此时,上面设备通讯提到的Websocket通讯就派上用场了。

我们先回忆一下,设备发现的流程:

3.4.1 启动设备发现#

智汀智慧首页点击添加设备进入到设备扫描发现页:

设备发现中...

首先根据后端需要发一个Websocket请求一定参数到服务,此时页面UI执行动画扫面展示效果,等待数据返回结果做处理

3.4.2 设备发现结束后#

接收到数据结果信息后根据是否成功展示反馈结果:

​ 1)成功有其他设备存在

则如图:

​ 2)失败或者不存设备

则如图:

3.4.3 实现原理代码#

在全局vuex已存Websocket方法

import { mapGetters } from 'vuex'
export default {  data() {    return {      isScan: true,      discoverList: [],      msgId: 1,      timer: null // 未发现显示定时器    }  },  computed: {    ...mapGetters(['websocket'])  },  mounted() {    this.scanDevice()  },  methods: {    // 扫描方法    scanDevice() {      this.isScan = true      // 发送发现指令      this.msgId = Date.now()      this.websocket.send({        domain: 'yeelight',        id: this.msgId,        service: 'discover'      })      // 接受消息      this.websocket.onmessage((data) => {        const msg = JSON.parse(data)        if (msg.id === this.msgId) {          if (!msg.result) {            return          }          const { device } = msg.result          const isExist = this.discoverList.find(item => item.identity === device.identity)          if (!isExist) {            clearTimeout(this.timer)            this.discoverList.push(device)          }        }      })      // 未发现设置显示      this.timer = setTimeout(() => {        this.isScan = false      }, 5000)    }  }}

3.5 业务功能:设备控制#

主要就是对设备一些操作控制比如:开启/关闭、光的亮度、音量、温度…等等调节操作机制。

设备控制页是一个独立单应用管理(独立插件包),插件开发下面将有单独介绍

设备控制页的展示效果:

设备控制同样是走Websocket通讯控制,根据不同类型设备传相对应参数获取当前控制状态

主要的实现原理:

  • 开关

点击传输一个参数类型 “off” 或者 “on” 根据接收的信息启动跟关闭

openLight(status) {  if (status) {    // 开灯    this.ws.send({      domain: 'yeelight',      id: 1,      service: 'switch',      service_data: {        device_id: this.deviceId,        power: 'on' // 开启      }    })  } else {    // 关灯    this.ws.send({      domain: 'yeelight',      id: 1,      service: 'switch',      service_data: {        device_id: this.deviceId,        power: 'off' // 关闭      }    })  }  this.isOn = status}
  • 调节亮度

拉拽温度进度条高温或者低温模式,向Websocket输送一个温度值,同样根据接收的结果启动亮度值

// 亮度变化lightChange(val) {  // 灯关闭不发送指令  if (!this.isOn) {    return  }  // 调节亮度  this.ws.send({    domain: 'yeelight',    id: 1,    service: 'set_bright',    service_data: {      device_id: this.deviceId,      brightness: val // 亮度值    }  })}
  • 调节色温

拉拽色温进度条,向Websocket输送一个色温值,同样根据接收的结果启动色温值

// 色温变化temperatureChange(val) {  // 灯关闭不发送指令  if (!this.isOn) {    return  }  // 调节色温  this.ws.send({    domain: 'yeelight',    id: 1,    service: 'set_color_temp',    service_data: {      device_id: this.deviceId,      color_temp: val // 色温值    }  })}

3.6 业务功能:权限控制#

权限管理

智能设备的安全性一大部分是依赖权限管理

今天我们来说说智汀家庭云是怎么做权限管理的

首先,我们看下角色管理的页面

高级设置页面

我们看下角色模板接口返回的数据

{    "status": 0,    "reason": "成功",    "data": {        "role": {            "permissions": {                "device": [{                    "permission": {                        "name": "添加设备",                        "action": "add",                        "target": "device",                        "attribute": ""                    },                    "allow": false                }],                "device_advanced": {                    "locations": [{                        "name": "小台灯^_^",                        "permissions": [{                            "permission": {                                "name": "控制开关",                                "action": "control",                                "target": "device-3",                                "attribute": "power"                            },                            "allow": false                        }]                    }]                },                "area": [{                    "permission": {                        "name": "生成邀请码",                        "action": "get",                        "target": "area",                        "attribute": "invite_code"                    },                    "allow": false                }],                "location": [{                    "permission": {                        "name": "添加房间/区域",                        "action": "add",                        "target": "location",                        "attribute": ""                    },                    "allow": false                }],                "role": [{                    "permission": {                        "name": "查看角色列表",                        "action": "get",                        "target": "role",                        "attribute": ""                    },                    "allow": false                }],                "scene": [{                    "permission": {                        "name": "新增场景",                        "action": "add",                        "target": "scene",                        "attribute": ""                    },                    "allow": false                }]            },            "is_manager": false        }    }}

结合页面和模板数据我们可以看到,权限模板接口主要返回分几个大模块

// 权限结构体{  "permission": {    "name": "新增场景",    "action": "add",    "target": "scene",    "attribute": ""  },  "allow": false}
{  "device": [{...}], // 设备相关控制权限  "area": [{...}], // 家庭/公司相关控制权限  "location": [{...}], // 房间/区域相关控制权限  "scene": [{...}], // 场景相关控制权限  "role": [{...}], // 角色相关控制权限  "device_advanced": {...}, // 设备的单独控制权限}

我们可以看到主要权限分几大块

  • 设备
  • 家庭
  • 场景
  • 角色
  • 设备操作

权限使用

我们设置好了权限之后,那怎么用这个权限呢?

我们先看下获取权限的接口(/users/userId/permissions)的返回值

{    "status": 0,    "reason": "成功",    "data": {        "permissions": {            "add_device": true, // 添加设备            "add_location": true, // 添加房间            "add_role": true, // 添加角色            "add_scene": true, // 添加场景            "control_device": true, // 控制设备            "control_scene": true, // 控制场景            "delete_area_member": true, // 删除家庭成员            "delete_device": true, // 删除设备            "delete_location": true, // 删除房间            "delete_role": true, // 删除角色            "delete_scene": true, // 删除场景            "get_area_invite_code": true, // 生成邀请码            "get_location": true, // 查看房间详情            "get_role": true, // 查看角色列表            "update_area_member_role": true, // 修改成员角色            "update_area_name": true, // 修改家庭名称            "update_device": true, // 更新设备            "update_location_name": true, // 修改房间名称            "update_location_order": true, // 调整房间顺序            "update_role": true, // 更新角色            "update_scene": true // 修改场景        }    }}

我们初始进入到页面的时候就要去调用该接口

然后把权限的结果保存在一个全局的变量permission

进入到页面就可以根据权限做不同的页面显示和操作控制

3.7 扩展开发:插件开发#

新建插件包

我们的插件包都是放在plugins文件夹下的

如果你需要新开发一个插件包,你可以在plugins文件夹下新建一个命为xxx的项目,

目录结构如下

├──plugins       ├─yeelight            // yeelight插件包          ├─assets             // 资源文件夹          ├─components         // 通用组件库          ├─router.js          // 路由文件          ├─App.vue           // 程序入口vue文件          ├─main.js           // 程序入口文件          └─views              // 页面文件

目录结构可以根据插件的复杂程度自行扩展

如何启动插件包项目?
建好插件包目录后,我们可以进到智汀专业版项目的根目录, 然后通过以下命令可以启动插件包项目

// xxx为你插件包的名称,例如启动yeelight插件包// npm run plugin yeelightnpm run plugin xxx

3.7.1 前端源码分析#

1. 我们看下主要的代码

插件包主要功能是实现智能设备的控制,智能设备的控制主要是通过Websocket发送指令去控制的

这里引用了智汀封装好的Websocket插件包ws-plugin

插件控制的相关指令可以点击这里插件模块


created() {  // 获取device_id参数  const { search, href } = window.location  const params = search ? this.getUrlParams(search) : this.getUrlParams(href)  this.deviceId = Number(params.device_id)  this.lightType = params.model || 'ceiling17'  this.token = params.token  if (params.name) {    this.deviceName = params.name  }  // 生成连接  const self = this  // 这里生成一个全局ws对象 用户和后端进行通讯  this.ws = new Socket({    url: `ws://192.168.0.84:8088/ws?token=${this.token}`,    onOpen() {      self.initData()    },    onMessage(data) {      self.handleMessage(data)    }  })}

这里有两个重要的变量,这两个参数都是从插件链接传进来的

  • token 用户的身份凭证,这里主要用来做权限控制
  • device_id 设备id,就是发送控制指令时需要知道是控制哪个设备

进入页面时我们要建立一个Websocket长连接,用来发送操作指令和同步更新设备状态

2. 如何做权限控制?

进入插件时我们要做好权限控制,无权限用户无法在插件页面操作。

当我们Websocket链接成功后,我们需要发一个指令去拿用户的操作权限。

指令如下:

this.ws.send({  domain: 'plugin',  id: this.perId,  service: 'get_actions',  service_data: {    device_id: this.deviceId  }})

发送指令后,后端会返回用户的权限信息,结构如下

{  id: 21620636295485, // 消息di  result: {    actions: {      set_bright: { // 操作权限名称        cmd: 'set_bright',        name: '调节亮度',        is_permit: true // 是否有权限 true false      },      set_color_temp: { // 操作权限名称        cmd: 'set_color_temp',        name: '调节色温',        is_permit: true // 是否有权限 true false      },      switch: { // 操作权限名称        cmd: 'switch',        name: '开关',        is_permit: true // 是否有权限 true false        }      }    },    success: true  }

然后拿到权限信息后就可以在页面做对应的操作

3. 如何控制设备?

我们也是通过发送指令的方法控制设备

例如:打开灯

// 开灯 deviceId就是设备id,从插件链接获得this.ws.send({  domain: 'yeelight',  id: 1,  service: 'switch',  service_data: {    device_id: this.deviceId,    power: 'on'  }})

其他操作也一样,只是操作的指令有所区别

4. 如何同步设备状态?

每个设备都有初始状态,我们怎么同步设备的初始状态呢

同样的也是发送指令

this.ws.send({  domain: 'yeelight',  id: this.stateId,  type: 'call_service',  service: 'state',  service_data: {    device_id: this.deviceId  }})

后端送到指令后会返回设备的初始状态,结构如下:

// 初始化数据返回结构体{  id: 8384959175, // 消息id  result: {    state: {      brightness: 0, // 灯亮度      color_temp: 0, // 灯色温      is_online: false, // 设备是否在线 true false      power: '' // 灯的开关状态 on 开 off 关    }  },  success: true}

拿到设备后,我们就可以对设备进行初始化值

5. 如何发布我们的插件?

当我们的插件包开发完成后,就打包编译我们的前端和后端文件, 填写我们的配置文件 结构如下:

然后在智汀家庭云上传我们的插件包,等审核通过后就可以看到我们的插件包了。

3.7.2 插件包集成#

打包我们的插件包

// xxx为你插件包的名称,例如打包yeelight插件包// npm run plugin yeelight buildnpm run plugin xxx build

打包后的文件在根目录下plugin文件下

插件的后端文件也要编译好

最终插件包的目录如下:

  • html文件夹是插件的页面文件,也就是(html,js,css等静态资源文件)
  • yeelight-plugin是插件后端的编译文件
  • config.yaml是插件的配置文件

我们看下插件的配置文件包括哪些信息

这些信息都是很重要的,不能遗漏。

我们的插件可以包含多个设备,每个设备有所属的品牌。

智汀用户通过添加安装我们的插件包就可以通过智汀app发现我们插件支持的设备,连接设备,操作我们的设备。

3.8 扩展开发:jsBridge使用#

3.8.1 概述#

智汀Web端专业版作为做智汀APP的辅助程序,所以免不了要跟APP那边做交互
jsBridge就是我们交互的桥梁,通过jsBridge我们可以很顺畅的跟APP那边进行交互

3.8.2 webview ua改造#

概述:为了区别于在APP内嵌入h5,加入特有的ua,用于判断是否在app内嵌入,在原有的ua上加上字符串“zhitingua”
我们可以通过判断ua就可以判断出我们的智汀专业版是否是在app内打开

/** *  判断h5是否在app内打开*/isApp() {  const ua = navigator.userAgent  if (ua.includes('zhitingua')) {    return true  }  return false}

3.8.3 JS-SDK文档#

APP直接在Webview内注入了一个zhiting的全局对象,并实现了invoke方法
我们只要调用此方法并传入相应的参数就可以调用app内提供的方法

3.8.3.1 查看app连网类型#
// res为回调结果,类型为json数据格式  zhiting.invoke('networkType', {}, (res) => {    // do something    // type为连网类型,可选值有 2g 3g 4g 5g wifi 空    // type为空时表示无网    var type = res.type})
3.8.3.2 设置标题属性#
// res为回调结果,类型为json数据格式  zhiting.invoke('setTitle',{    title: '标题名称', // 标题名称    color: '#333333', // 标题颜色,rgb值    background: '#ffffff', // 标题栏背景色,rgb值    isShow: true // 是否显示标题栏,默认显示})
3.8.3.3 获取用户信息#
// res为回调结果,类型为json数据格式  zhiting.invoke('getUserInfo', {}, (res) => {    // do something    // token为app保存的登录凭证    // userId为当前用户的id    var userId = res.userId    var token = res.token})
3.8.3.4 是否是专业版APP#
// res为回调结果,类型为json数据格式  zhiting.invoke('isProfession', {}, (res) => {    // result为是否专业版判断结果 true false    var isProfession = res.result})

3.9 部署应用#

部署应用是指编译或构建代码并将生成的 JavaScript、CSS 和 HTML 托管到 Web 服务器上的过程。

构建和托管你的应用

我们的项目开发完后,怎么发布,首先要构建我们的项目

npm run build

在根目录执行该命令后,项目的构建文件会输出到dist文件夹

我们下一步就是把我们的dist文件夹托管在服务器上,配置好请求转发和域名

至此,我们的应用已经部署成功了