Commit bcc97584f08cfcccc057f3588ab5659cb85feab8

Authored by xp.Huang
1 parent ee6833b3

docs: v1.2.0_release

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