Commit 3dad76cc76c9df78cb3248737e2302a84dc24618

Authored by 黄 x
1 parent ba860f06

feat: commit ThingsKit README-DEFAULT.md

Showing 1 changed file with 548 additions and 0 deletions
  1 +## 准备
  2 +
  3 +- [node](http://nodejs.org/)[git](https://git-scm.com/) -项目开发环境
  4 +- [layui](http://layui.org.cn/doc/index.html) - UI组件库
  5 +- [jquery](https://jquery.cuishifeng.cn/index.html) - jquery库
  6 +- [video.js](https://videojs.com/) - video.js库
  7 +- [axios](https://axios-http.com/) - http请求库
  8 +- [crypto-js](http://github.com/brix/crypto-js) - 加解密库
  9 +- [echarts](https://echarts.apache.org/examples/zh/index.html) - 可视化图表库
  10 +
  11 +## 目录文件及重要文件说明
  12 +
  13 +### 目录文件
  14 +
  15 +```
  16 +/src/main/webapp/js
  17 +├── api api请求管理
  18 +├── config 项目配置 oss 配置等
  19 +├── const 项目中的常量
  20 +├── diagramly
  21 +│   ├── sidebar
  22 +│   │   └── thingskit 项目新增的元件库
  23 +├── grapheditor 图形编辑器有关文件
  24 +├── plugin 项目中使用的插件
  25 +│   ├── ace
  26 +│   ├── axios
  27 +│   ├── crypto-js
  28 +│   ├── echarts
  29 +│   ├── layui
  30 +│   │   ├── css
  31 +│   │   │   └── modules
  32 +│   │   │   ├── laydate
  33 +│   │   │   │   └── default
  34 +│   │   │   └── layer
  35 +│   │   │   └── default
  36 +│   │   └── font
  37 +│   └── videos
  38 +```
  39 +
  40 +### 重要文件说明
  41 +
  42 +#### 元件库开发
  43 +
  44 +![image-20230106142352191](https://oss.yuntengcloud.com//iotdocs/img/image-20230106142352191.png)
  45 +
  46 +##### 文件位置
  47 +
  48 +```
  49 +.
  50 +├── src
  51 +│   └── main
  52 +│ └── webapp
  53 +│ └── js
  54 +| └── diagramly
  55 +│   └── Devel.js 加载元件库文件
  56 +│   └── sidebar
  57 +| └── Sidebar.js 各图形库加载图形
  58 +| └── thingskit 自定义元件库文件
  59 +| └── Sidebar-Engine.js 元件库文件
  60 +├── etc
  61 +    └── build
  62 + └── build.xml 打包自定义元件库
  63 +```
  64 +
  65 +##### 源码介绍
  66 +
  67 +###### 创建元件库
  68 +
  69 +创建需要自定义新增的元件库 !!! 自定义属性 用于确定组件类型件组件和数据绑定面板关联
  70 +
  71 +```js
  72 +// Sidebar.Engine.js
  73 +// src/main/webapp/js/diagramly/sidebar/thingskit/Sidebar-Engine.js
  74 +(function () {
  75 + // Adds Atlassian shapes
  76 + // conduit 管道
  77 + Sidebar.prototype.addConduitPalette = function () {
  78 + // 组件归类 决定数据面板中组件有哪些操作面板
  79 + const { COMPONENT_TYPE } = this.enumCellBasicAttribute
  80 + const { DEFAULT } = this.enumComponentType
  81 +
  82 + // 图形库信息
  83 + const gn = 'mxgraph.engine';// 图形库id 后续注册时需要使用
  84 + const dt = 'engine'; //图 形库id 后续注册时需要使用
  85 + const label = '发动机'
  86 +
  87 + const width = 66;
  88 + const height = 74;
  89 + const staticPath = `${Proxy_Prefix}/img/lib/thingskit/`
  90 + const prefix = 'image;image=img/lib/thingskit/'
  91 + const defaultStyle = ';imageAspect=0;'
  92 + this.setCurrentSearchEntryLibrary(dt);
  93 +
  94 + // !!!自定义属性 当前设置该组件库为默认类型
  95 + const cellAttribute = {
  96 + [COMPONENT_TYPE]: DEFAULT
  97 + }
  98 +
  99 + const graphPathLib = [
  100 + { name: '3-D 发动机.svg', path: 'engine/3-D 发动机.svg' },
  101 + ]
  102 +
  103 + const lib = graphPathLib.map(item => {
  104 + item.staticPath = staticPath + item.path
  105 + return item
  106 + })
  107 +
  108 + const fns = graphPathLib.map(item => {
  109 + return this.addEntry(this.getTagsForStencil(gn, item.name, dt).join(' '), mxUtils.bind(this, function () {
  110 + const cell = new mxCell('', new mxGeometry(0, 0, width, height), `${prefix}${item.path}${defaultStyle}`);
  111 + cell.setVertex(true)
  112 + this.setCellAttributes(cell, cellAttribute)
  113 + return this.createVertexTemplateFromCells([cell], cell.geometry.width, cell.geometry.height, item.name);
  114 + }));
  115 + })
  116 +
  117 + this.setVariableImageLib(dt, label, lib)
  118 +
  119 + this.addPaletteFunctions(dt, label, false, fns);
  120 +
  121 + this.setCurrentSearchEntryLibrary();
  122 + };
  123 +})();
  124 +
  125 +```
  126 +
  127 +>2. 将元件库导入
  128 +
  129 +```js
  130 +// src/main/webapp/js/diagramly/Devel.js
  131 +// Devel.js
  132 +// 将Sidebar-Engine.js引入
  133 +mxscript(drawDevUrl + 'js/diagramly/sidebar/thingskit/Sidebar-Engine.js');
  134 +```
  135 +
  136 +>3. 将元件库加入到需要打包的文件队列中
  137 +
  138 +```xml
  139 + <!-- build.xml -->
  140 + <!-- /etc/build/build.xml -->
  141 + <!-- 将Sidebar-Engine.js添加至需要打包的文件中去 -->
  142 +<jscomp compilationLevel="simple" debug="false" forceRecompile="true" output="${basedir}/sidebar.min.js">
  143 + ...
  144 + <file name="./thingskit/Sidebar-Engine.js" />
  145 +</jscomp>
  146 +```
  147 +
  148 +>4. 将元件库注册到侧边栏
  149 +
  150 +```js
  151 +// Sidebar.js
  152 +// src/main/webapp/js/diagramly/sidebar/Sidebar.js
  153 +// 将图形库id加入Siderbar中
  154 +Sidebar.prototype.configuration = [
  155 + ...
  156 + { id: 'engine' } // 图形库id
  157 +]
  158 +// 添加到更多图形中去
  159 +Sidebar.prototype.init = function () {
  160 + ...
  161 + var thingskitEntries = [
  162 + // 注册至到更多图形中
  163 + { title: "发动机", id: 'engine', image: IMAGE_PATH + '/thingskit/发动机.png' }
  164 + ];
  165 + }
  166 +// 添加到侧边栏中
  167 +Sidebar.prototype.initPalettes = function () {
  168 + ...
  169 + // 发动机
  170 + this.addEnginePalette();
  171 +}
  172 +```
  173 +
  174 +#### 数据绑定面板开发
  175 +
  176 +![image-20230106151624286](https://oss.yuntengcloud.com//iotdocs/img/image-20230106151624286.png)
  177 +
  178 +![image-20230106151717295](https://oss.yuntengcloud.com//iotdocs/img/image-20230106151717295.png)
  179 +
  180 +##### 文件位置
  181 +
  182 +```
  183 +/src/main/webapp/js
  184 +├── grapheditor
  185 +│   ├── Format.js
  186 +```
  187 +
  188 +##### 源码介绍
  189 +
  190 +
  191 +
  192 +>控制元件拥有哪些数据绑定面板,通过 this.setComponentPermission 方法设置各个类型的元件有哪些数据绑定面板。
  193 +
  194 +```js
  195 +// Sidebar.js
  196 +// src/main/webapp/js/diagramly/sidebar/Sidebar.js
  197 + Sidebar.prototype.init = function () {
  198 + const { LINE_CHART_EXPAND, BAR_CHART_EXPAND, DASHBOARD_CHART_EXPAND, DYNAMIC_EFFECT, DATA_SOURCE, VAR_IMAGE, INTERACTION, VIDEO: VIDEO_PANEL, SWITCH_STATE_SETTING, ONLY_SINGLE_EVENT, RUNNING_AND_STOP } = this.enumPermissionPanel
  199 + const { LINE, LINE_CHART, REAL_TIME, TITLE, VARIABLE, DEFAULT, BAR_CHART, VIDEO, SWITCH, PARAMS_SETTING_BUTTON, DASHBOARD_CHART, IMAGE } = this.enumComponentType
  200 +
  201 + // 水流类型元件拥有水流效果面板与数据东西面板
  202 + this.setComponentPermission(LINE, [RUNNING_AND_STOP, DYNAMIC_EFFECT])
  203 + // 默认类型的元件拥有数据动效面板
  204 + this.setComponentPermission(DEFAULT, [DYNAMIC_EFFECT])
  205 + this.setComponentPermission(REAL_TIME, [DYNAMIC_EFFECT])
  206 + // 标题类型的元件拥有数据交互面板与动态数据面板
  207 + this.setComponentPermission(TITLE, [INTERACTION, DYNAMIC_EFFECT])
  208 + this.setComponentPermission(VAR_IMAGE, [INTERACTION, VAR_IMAGE])
  209 + this.setComponentPermission(VARIABLE, [DATA_SOURCE, INTERACTION, DYNAMIC_EFFECT])
  210 + this.setComponentPermission(BAR_CHART, [DATA_SOURCE, BAR_CHART_EXPAND])
  211 + this.setComponentPermission(LINE_CHART, [DATA_SOURCE, LINE_CHART_EXPAND])
  212 + this.setComponentPermission(DASHBOARD_CHART, [DATA_SOURCE, DASHBOARD_CHART_EXPAND])
  213 + this.setComponentPermission(VIDEO, [VIDEO_PANEL])
  214 + this.setComponentPermission(SWITCH, [DATA_SOURCE, SWITCH_STATE_SETTING])
  215 + this.setComponentPermission(PARAMS_SETTING_BUTTON, [DATA_SOURCE, ONLY_SINGLE_EVENT])
  216 + this.setComponentPermission(IMAGE, [DATA_SOURCE])
  217 + }
  218 +```
  219 +
  220 +
  221 +
  222 +>Format.js 根据元件拥有的权限渲染数据绑定面板,如上图开关元件与变量元件数据绑定面板的差异化渲染。
  223 +
  224 +```js
  225 +// 数据绑定面板的开发源码均在此方法中
  226 +// 开发使用layui与jquery库
  227 +// src/main/webapp/js/grapheditor/Format.js
  228 +DataFormatPanel.prototype.addDataFont = function () {
  229 +
  230 + // 该方法用于根据元件类型渲染不同的面板
  231 + async function initNode() {
  232 + const basicAttr = sidebarInstance.enumCellBasicAttribute
  233 + const permissionKey = sidebarInstance.enumPermissionPanel
  234 + const { LINE_CHART, BAR_CHART, DASHBOARD_CHART } = Sidebar.prototype.enumComponentType
  235 +
  236 + const renderMapping = {
  237 + // 元件拥有数据源权限
  238 + [permissionKey.DATA_SOURCE]: createDataSourcePanel,
  239 + // 元件拥有图表数据绑定面板
  240 + [permissionKey.LINE_CHART_EXPAND]: createChartBindPanel.bind(this, LINE_CHART),
  241 + [permissionKey.BAR_CHART_EXPAND]: createChartBindPanel.bind(this, BAR_CHART),
  242 + // 元件拥有数据源拓展面板
  243 + [permissionKey.DASHBOARD_CHART_EXPAND]: createChartBindPanel.bind(this, DASHBOARD_CHART),
  244 + // 元件拥有数据交互面板
  245 + [permissionKey.INTERACTION]: createInteractionPanel,
  246 + // 元件拥有数据动效面板
  247 + [permissionKey.DYNAMIC_EFFECT]: createDynamicEffectPanel,
  248 + // 元件库拥有变量图片选项
  249 + [permissionKey.VAR_IMAGE]: createVarImagePanel,
  250 + // 元件库拥有视频配置面板
  251 + [permissionKey.VIDEO]: createVideoBindPanel,
  252 + // 元件库拥有开关状态切换面板
  253 + [permissionKey.SWITCH_STATE_SETTING]: createSwitchStateSettingPanel,
  254 + }
  255 +
  256 + // 根据元件的权限渲染数据绑定面板
  257 + function permissionRender() {
  258 + try {
  259 + const cell = vertices[0]
  260 + const permission = graph.getAttributeForCell(cell, basicAttr.COMPONENT_TYPE)
  261 + const needDisplayPanel = sidebarInstance.getComponentPermission(permission)
  262 + for (const key of needDisplayPanel) {
  263 + renderMapping[key]()
  264 + }
  265 + if (needDisplayPanel.length) createSubmitPanel()
  266 + UseLayUi.nextTick(() => form.render())
  267 + } catch (e) {
  268 + throw Error('component permission setting has some problem, please check your component permission bind on "Sidebar.prototype.init" method setComponentPermission')
  269 + }
  270 + }
  271 + }
  272 +
  273 + // 该方法用于创建提交按钮以及根据元件类型做提交参数转换处理
  274 + function createSubmitPanel() {
  275 + ...
  276 + const value = getValueOnSubmit(field)
  277 + }
  278 +
  279 + // 根据元件类型做上传参数转换
  280 + function getValueOnSubmit(field) {
  281 + const basicAttr = sidebarInstance.enumCellBasicAttribute
  282 + const componentType = sidebarInstance.enumComponentType
  283 + ...
  284 + const renderMapping = {
  285 + [componentType.BAR_CHART]: getChartSubmitValue,
  286 + [componentType.DEFAULT]: getSubmitValue,
  287 + [componentType.VIDEO]: getVideoSubmitValue,
  288 + [componentType.SWITCH]: getSwitchSubmitValue,
  289 + }
  290 +
  291 + ...
  292 +
  293 + // 处理方法
  294 + function getChartSubmitValue() {}
  295 + function getSubmitValue() {}
  296 + function getVideoSubmitValue() {}
  297 + function getSwitchSubmitValue() {}
  298 +
  299 +}
  300 +```
  301 +
  302 +>WebSocket接收数据,前端根据页面组件做发布订阅处理。
  303 +
  304 +```js
  305 +// src/main/webapp/js/grapheditor/Format.js
  306 +
  307 +// 事件中心 发布订阅
  308 +class EventCenter {
  309 + ...
  310 +}
  311 +// socket 相关
  312 +class Ws {
  313 + ....
  314 +}
  315 +
  316 +// 调度中心
  317 +class DispatchCenter {
  318 + // ...
  319 + // 初始化方法
  320 + async init(editorUi, currentPage) {
  321 + // 创建事件中心
  322 + this.createEventBus()
  323 + // 获取页面所有节点
  324 + this.saveContentInfo(editorUi, currentPage)
  325 + // 建立socket连接
  326 + this.connectSocket()
  327 + // 获取页面中所有绑定数据源的组件记录
  328 + await this.getContentDataNode()
  329 + // 实例化各个处理模块 各个处理模块
  330 + this.dataSourceHandlerInstance = new HandleDataSource(this)
  331 + this.dataInteractionInstance = new HandleDataInteraction(this)
  332 + this.dynamicEffectInstance = new HandleDynamicEffect(this)
  333 + // 更新队列
  334 + this.updateQueueInstance = new UpdateQueue(this)
  335 + this.sendSubscribeMessage()
  336 + }
  337 + // 创建订阅消息模型 将 消息分类汇总过滤,重复数据源只订阅一条消息
  338 + generateSubscribeMessage() {
  339 + // ...
  340 + }
  341 +
  342 + // 根据socket接受消息 调用不同的处理逻辑
  343 + socketOnMessage() {
  344 + // ...
  345 + // 查找当前订阅id中,绑定的相同数据源
  346 + const subList = this.subscribeIdMapping.get(subscriptionId)
  347 + subList.forEach(item => {
  348 + // ...
  349 + // 根据绑定的数据源判断调用的处理逻辑 例如调用 HandleDataSource 实例中的updateCommonDataSource 方法
  350 + this.dataSourceHandlerInstance.updateCommonDataSource(message, item)
  351 + }
  352 + }
  353 +
  354 +}
  355 +
  356 +
  357 +
  358 +// 处理数据源
  359 +class HandleDataSource {
  360 + ...
  361 + // 根据元件类型分发处理逻辑
  362 + updateCommonDataSource(message, record) {
  363 + const { nodeId, attr } = record
  364 + const node = this.getNodeByCmdId(nodeId)
  365 + node && this.updatePage(() => {
  366 + const { data } = message
  367 + const type = this.getComponentType(node)
  368 + // ...
  369 + // 元件类型为图片时
  370 + if (type === this.componentType.IMAGE) {
  371 + this.handleImageComponent(message, record)
  372 + return
  373 + }
  374 + }, node)
  375 + // ...
  376 + }
  377 + // 图片处理逻辑
  378 + handleImageComponent(message, record) {
  379 + const { data = {} } = message
  380 + const { nodeId, attr } = record
  381 + const node = this.getNodeByCmdId(nodeId)
  382 + const [[_timespan, receiveValue] = []] = data[attr] || []
  383 + this.updatePage(() => {
  384 + // 更新节点中的图片
  385 + node.setAttribute('label', `<img class="basic-component__image" alt="图片" src="${receiveValue}" />`)
  386 + }, node)
  387 + }
  388 +}
  389 +// 处理数据交互
  390 +class HandleDataInteraction {
  391 + ...
  392 +}
  393 +// 处理数据动效
  394 +class HandleDynamicEffect {
  395 + ...
  396 +}
  397 +// 建立更新队列
  398 +class UpdateQueue {
  399 + ...
  400 +}
  401 +```
  402 +
  403 +
  404 +
  405 +#### const.js
  406 +
  407 +##### 文件位置
  408 +
  409 +```
  410 +/src/main/webapp/js
  411 +├── const
  412 +│   ├── const.js // 定义项目中的常量
  413 +│   ├── persistentStorage.js // 加解密storage,与后台管理系统一致
  414 +```
  415 +
  416 +##### 源码介绍
  417 +
  418 +> 1. const.js 常量名
  419 +
  420 +```
  421 +GLOBAL_TOKEN 访问令牌:
  422 + 用于请求中携带
  423 + 来源于后台管理系统中存储在storage中的值,storage中的值涉及在生产环境和开发环境中是否加解密问题,加解密方式应与后台管理系统中 一致。
  424 +
  425 +GLOBAL_PLATFORM_INFO 平台信息
  426 + 用于项目中需要使用平台信息的场景,例如首屏加载时的loading,显示不同平台的平台名。
  427 +
  428 +GLOBAL_WS_URL socket地址
  429 + 用于项目中socket连接
  430 +
  431 +hasSavePermission 检查是否权限
  432 + 用于检查组态中时候拥有写入的权限
  433 +```
  434 +
  435 +> 2. persistentStorage.js 加解密storage相关
  436 +
  437 +### 配置文件
  438 +
  439 +#### OSS打包配置
  440 +
  441 +##### 文件位置
  442 +
  443 +```
  444 +/src/main/webapp/js
  445 +├── config
  446 +│   ├── config.js
  447 +```
  448 +
  449 +##### 源码介绍
  450 +
  451 +```js
  452 +/**
  453 + * @description 加载OSS文件时使用的oss文件路径
  454 + */
  455 +const OSS_Prefix = 'https://oss.xxx.com/'
  456 +/**
  457 + * @description 是否使用OSS文件 开启时生产环境中使用oss服务的文件将从oss服务器中加载。
  458 + */
  459 +const Enable_OSS = false
  460 +
  461 +/**
  462 + * @description 代理配置项
  463 + */
  464 +const Proxy_Prefix = window.location.pathname.startsWith('/') ? window.location.pathname.replace(/\/$/, '') : window.location.pathname
  465 +```
  466 +
  467 +```js
  468 +// index.html
  469 +// 例如在index.html中使用Enable_OSS判断是否开启OSS模式,开启则通过 OSS_Prefix 配置的oss服务器地址中加载文件
  470 +const appMinSrc = Enable_OSS ? `${OSS_Prefix}app.min.js?v=${releaseVersion}` : `js/app.min.js?v=${releaseVersion}`
  471 +```
  472 +
  473 +#### 项目打包配置
  474 +
  475 +##### gulpfile.js执行流程
  476 +
  477 +> 项目基于draw.io二次开发,使用ant进行打包,打包时不能编译es6+语法,如async,await, class等语法,使用新语法时会导致打包失败,因此先使用glup先编译,在通过ant打包。
  478 +
  479 +```js
  480 +// 1.打包前先复制需要编译的文件源码
  481 +function copyFile() {}
  482 +// 2.编译文件
  483 +function complieFormat() {}
  484 +// 3.引入的文件增加版本号(用于打包部署后服务器上文件不更新)
  485 +function generatoreVersion(){}
  486 +// 4.构建war包
  487 +async function buildWar() {}
  488 +// 5.复制需要上传到oss服务器中的文件
  489 +function copyFileUsageOssServer() {}
  490 +// 6.还原文件
  491 +function reductionFile() {}
  492 +// 7.清除复制的文件
  493 +function clean() {}
  494 +```
  495 +
  496 +## 开发环境搭建及源码运行
  497 +
  498 +## 打包部署
  499 +
  500 +### 准备工作
  501 +
  502 +1. [JDK]([Download Java for macOS](https://www.java.com/zh-CN/download/))安装与环境变量配置(项目使用JDK11) [安装文档](https://blog.csdn.net/Marvin_996_ICU/article/details/106240065)
  503 +2. [Apache Ant]([Apache Ant - Welcome](https://ant.apache.org/))安装与环境变量配置(项目使用Ant version 1.10.12 版本) [安装文档](https://www.yiibai.com/ant/ant_environment.html#) ,安装Apache Ant 1 确保将JAVA_HOME环境变量设置到安装JDK的文件夹。 2 下载的二进制文件从 http%3A%2F%2Fant.apache.org 3,创建一个名为ANT_HOME,一个新的环境变量指向Ant的安装文件夹,在 c%3Aapache-ant-1.8.2-bin 文件夹。 5 附加的路径Apache Ant批处理文件添加到PATH环境变量中。 在我们的例子是 c%3Aapache-ant-1.8.2-binbin文件夹。))
  504 +3. [Node.js]([Node.js (nodejs.org)](https://nodejs.org/en/))安装(项目使用v16.15.0版本)
  505 +
  506 +### 运行打包
  507 +
  508 +```sh
  509 +# 安装依赖
  510 +npm install
  511 +
  512 +# 运行打包 运行后开始打包 预计耗时1min
  513 +npm build
  514 +
  515 +```
  516 +
  517 +> 打包成功后控制台输出结果与包存放位置,oss文件存放需要上传至oss服务器的js文件。
  518 +
  519 +![image-20230106143620675](https://oss.yuntengcloud.com//iotdocs/img/image-20230106143620675.png)
  520 +
  521 +![image-20230106143929180](https://oss.yuntengcloud.com//iotdocs/img/image-20230106143929180.png)
  522 +
  523 +
  524 +
  525 +## 参考文档
  526 +
  527 +* [mxGraph]([API Specification (jgraph.github.io)](https://jgraph.github.io/mxgraph/docs/js-api/files/index-txt.html)) - mxGraph 库 Api 文档
  528 +* [Draw.io常见问题]([diagrams.net Frequently Asked Questions](https://www.diagrams.net/doc/faq/)) - Draw.io常见问题
  529 +* [掘金二开文章一]([mxgraph配合draw.io再次使用总结 - 掘金 (juejin.cn)](https://juejin.cn/post/7065578041140838436)) - 掘金二次开发参考文章
  530 +* [掘金二开文章二]([DrawIO 二开 —— 是时候给你的 ProcessOn 充值终身 VIP 了 - 掘金 (juejin.cn)](https://juejin.cn/post/7017686432009420808#heading-8)) - 掘金二次开发参考文章
  531 +* [mxGraph二开文章一]([mxGraph入门 (gitee.io)](https://bibichuan.gitee.io/posts/3337d458.html)) - mxGraph二次开发参考文章
  532 +* [mxGraph二开文章二]([mxGraph教程 - 李理的博客 (fancyerii.github.io)](http://fancyerii.github.io/2019/03/26/mxgraph/)) - mxGraph二次开发参考文章
  533 +
  534 +## FAQ
  535 +
  536 +1. 打包过程中发生失败时,可能回导致需要gulp编译的文件被覆盖。应检查需要编译的文件是否已经被gulp编译。
  537 +
  538 + 左侧文件则被gulp编译,失败时则到dist目录中找到未被编译的文件或者通过git进行回滚,在解决打包失败的原因后,再次打包。
  539 +
  540 + ![image-20230106145422197](https://oss.yuntengcloud.com//iotdocs/img/image-20230106145422197.png)
  541 +
  542 + ![image-20230106145519954](https://oss.yuntengcloud.com//iotdocs/img/image-20230106145519954.png)
  543 +
  544 +2. 打包后index.html会发生变更,引入文件的版本号修改,导致index.html被git追踪,可以通过将index.html加入git忽略文件中去。
  545 +
  546 + ![image-20230106144551477](https://oss.yuntengcloud.com//iotdocs/img/image-20230106144551477.png)
  547 +
  548 +3. 组态启动后的端口因与后台管理端的端口保持一致。
\ No newline at end of file
... ...