浏览代码

节点模块制作

asd26269546 2 年之前
父节点
当前提交
f7247ffbd0

+ 3 - 0
package.json

@@ -20,13 +20,16 @@
     "js-base64": "^2.5.1",
     "js-cookie": "^2.2.0",
     "js-md5": "^0.7.3",
+    "jsplumb": "^2.15.6",
     "mockjs": "^1.0.1-beta3",
     "node-gyp": "^5.0.6",
     "nprogress": "^0.2.0",
+    "panzoom": "^9.4.3",
     "portfinder": "^1.0.23",
     "script-loader": "^0.7.2",
     "vue": "^2.6.10",
     "vue-axios": "^2.1.2",
+    "vue-click-outside": "^1.1.0",
     "vue-i18n": "^8.7.0",
     "vue-router": "^3.0.1",
     "vuex": "^3.1.1"

+ 55 - 0
src/api/process/generalProcess.js

@@ -0,0 +1,55 @@
+import request from '@/router/axios'
+
+// 通用流程-分页
+export function page(data = {}) {
+    return request({
+        url: '/api/service-flow/processInfo/page',
+        method: 'post',
+        data: data,
+    })
+}
+
+// 通用流程-添加
+export function add(data = {}) {
+    return request({
+        url: '/api/service-flow/processInfo/add',
+        method: 'post',
+        data: data,
+    })
+}
+
+// 通用流程-修改
+export function edit(data = {}) {
+    return request({
+        url: '/api/service-flow/processInfo/edit',
+        method: 'post',
+        data: data,
+    })
+}
+
+// 通用流程-删除
+export function dlt(data = {}) {
+    return request({
+        url: '/api/service-flow/processInfo/delete',
+        method: 'post',
+        data: data,
+    })
+}
+
+// 通用流程-详情
+export function dtl(data = {}) {
+    return request({
+        url: '/api/service-flow/processInfo/details',
+        method: 'post',
+        data: data,
+    })
+}
+
+// 业务服务下拉
+export function getServiceNameList(data = {}) {
+    return request({
+        url: '/api/service-flow/processInfo/serviceNameList',
+        method: 'post',
+        data: data,
+    })
+}

+ 5 - 1
src/components/form-test/index.vue

@@ -140,6 +140,7 @@
                type: 'upload'
              -->
             <!-- <div v-else-if="value.type === 'upload'">
+              
               <file-select v-if="!value.disabled" v-model="fileData[key]" @selectAfter="selectFile(key)"/>
               <fly-img style="padding-top: 10px" :src="insideData[key]" :del="!value.disabled"
                        @close="removeFile(key)"/>
@@ -317,7 +318,10 @@ export default {
         }
       }
     },
-
+    //初始化
+    reset() {
+      this.$refs.form.resetFields();
+    },
     // el-input标签处理
     inputHandle(key, value) {
       this.insideConfig[key].placeholder = value.placeholder || '请输入' + value.label

+ 20 - 0
src/router/page/index.js

@@ -68,11 +68,31 @@ export default [
         props: true,
       },
       {
+        path: '/generalProcess',
+        name: '通用流程',
+        component: () => import(/* webpackChunkName: "page" */ '@/views/process/generalProcess/index'),
+        props: true,
+      },
+      {
+        path: '/processNode',
+        name: '节点列表',
+        component: () => import(/* webpackChunkName: "page" */ '@/views/process/processNode/index'),
+        props: true,
+      },
+      {
+        path: '/nodeConfig/:id',
+        name: '节点配置',
+        component: () => import(/* webpackChunkName: "page" */ '@/views/process/processNode/nodeConfig'),
+        props: true,
+      },
+      {
         path: ':routerPath',
         name: 'iframe',
         component: () => import(/* webpackChunkName: "page" */ '@/components/iframe/main'),
         props: true,
       },
+      
+      
     ],
   },
   {

+ 9 - 0
src/util/util.js

@@ -365,3 +365,12 @@ export const downloadFileBase64 = (path, name) => {
     }
   };
 }
+
+//生成指定长度的唯一ID
+export function GenNonDuplicateID(randomLength) {
+  return Number(
+      Math.random()
+      .toString()
+      .substr(3, randomLength) + Date.now()
+  ).toString(36);
+}

+ 47 - 50
src/views/management/video-setting/index.vue

@@ -2,11 +2,11 @@
     <div class="video-setting">
       <el-row>
         <el-col :span="8">
-          <tree></tree>
+          <tree @treeClick="reload"></tree>
         </el-col>
         <el-col :span="16">
           <el-card class="box-card">
-            <test v-model="queryParams" :form-config="queryForm"></test>
+            <test ref="req" v-model="req" :form-config="queryForm"></test>
         
             <el-table :data="tableList" v-loading="loading">
               <el-table-column :label="$t('management.video_setting.videoUrl')" align="center" prop="videoUrl" />
@@ -23,10 +23,10 @@
               </el-table-column>
             </el-table>
         
-            <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
+            <pagination v-show="total > 0" :total="total" :page.sync="req.pageNum" :limit.sync="req.pageSize" @pagination="getList" />
         
-            <el-dialog title="添加设备" v-if="open" :visible.sync="open" width="30%">
-              <test ref="test" v-model="dialogParams" :form-config="dialogForm" :insideRules="dialogRules"></test>
+            <el-dialog title="添加设备" :visible.sync="open" width="30%">
+              <test ref="form" v-model="dialogParams" :form-config="dialogForm" :insideRules="dialogRules"></test>
             </el-dialog>
           </el-card>
         </el-col>
@@ -47,7 +47,7 @@
     data() {
       return {
         
-        queryParams: {
+        req: {
           pageNum: 1,
           pageSize: 10,
           keyword: '',
@@ -58,45 +58,49 @@
         queryForm: {
           keyword: {
             label: this.$t('management.video_setting.keyword'),
-            span: 6,
-          },
-          tdaApplicationId: {
-            label: this.$t('management.video_setting.tdaApplicationId'),
-            span: 6,
-            type: 'select',
-            data: [],
-            keyName: 'id',
-            labelName: 'appName',
-          },
-          tdaProductId: {
-            label: this.$t('management.video_setting.tdaProductId'),
-            span: 6,
-            type: 'select',
-            data: [],
-            keyName: 'id',
-            labelName: 'name',
+            span: 4,
           },
+          // classifyId: {
+          //   span: 4,
+          //   label: this.$t('management.video_setting.type'),
+          //   type: 'select',
+          //   data: [
+          //     {
+          //       label: this.$t(
+          //         'management.video_setting.treeAddType1'
+          //       ),
+          //       type: 1,
+          //     },
+          //     {
+          //       label: this.$t(
+          //         'management.video_setting.treeAddType2'
+          //       ),
+          //       type: 2,
+          //     },
+          //   ],
+          //   keyName: 'type',
+          //   labelName: 'label',
+          // },
           operation: {
             // 搜索按钮操作
             query: () => {
-              this.queryParams.pageNum = 1
+              this.req.pageNum = 1
               this.getList()
             },
             // 重置按钮操作
             reset: () => {
-              this.queryParams = { pageNum: 1, pageSize: 10, keyword: '', tdaApplicationId: '', name: '' }
-              this.queryParams.pageNum = 1
+              this.$refs.req.reset()
               this.getList()
             },
           },
           otherButton: {
             list: [
               {
-                name: this.$t('management.video_setting.addEquipment'),
+                name: this.$t('management.video_setting.addModal'),
                 methodsText: 'add',
                 type: 'primary',
                 add: () => {
-                  this.handleAdd()
+                  this.open = true
                 },
               },
             ],
@@ -108,20 +112,17 @@
         open: false,
         title: '',
         dialogParams: {
-          tdaApplicationId: '',
-          tdaProductId: '',
-          nodeId: '',
-          deviceName: '',
-          secret: 'Fjhx@2012#',
+          fileInfoList: '',
+          videoClassifyId: '',
+          title: '',
+          richText: '',
         },
         dialogForm: {
           loadingStatus: false,
-          tdaApplicationId: {
+          fileInfoList: {
             label: this.$t('management.video_setting.applicationName'),
-            type: 'select',
+            type: 'upload',
             data: [],
-            keyName: 'id',
-            labelName: 'appName',
           },
           tdaProductId: {
             label: this.$t('management.video_setting.productName'),
@@ -149,6 +150,7 @@
                 name: this.$t('cancelText'),
                 methodsText: 'cancel',
                 cancel: () => {
+                  this.$refs.form.reset()
                   this.open = false
                 },
               },
@@ -177,16 +179,20 @@
     },
     mounted() {
       this.getList()
-      this.getTree()
     },
     watch: {
       
     },
     methods: {
-      
+      reload(row){
+        console.log(row)
+        this.req.pageNum = 1
+        this.req.classifyId = row.id
+        this.getList()
+      },
       getList() {
         this.loading = true
-        API.videoInfoPage(this.queryParams).then(
+        API.videoInfoPage(this.req).then(
           (res) => {
             this.total = res.data.data.total
             this.tableList = res.data.data.records
@@ -198,16 +204,6 @@
           },
         )
       },
-      handleAdd() {
-        this.dialogParams = {
-          tdaApplicationId: '',
-          tdaProductId: '',
-          nodeId: '',
-          deviceName: '',
-          secret: 'Fjhx@2012#',
-        }
-        this.open = true
-      },
       handleSubmit() {
         this.$refs.test.$refs['form'].validate((valid) => {
           if (valid) {
@@ -215,6 +211,7 @@
             API.videoInfoAdd(this.dialogParams).then(
               () => {
                 this.msgSuccess(this.$t('addSuccess'))
+                this.$refs.form.reset()
                 this.open = false
                 this.dialogForm.loadingStatus = false
                 this.getList()

+ 18 - 3
src/views/management/video-setting/tree.vue

@@ -14,7 +14,7 @@
 					<el-button
 						type="text"
 						size="mini"
-						@click="() => treeDelete(node, data)"
+						@click="() => treeDelete(data)"
 					>
 						{{ $t('delete') }}
 					</el-button>
@@ -134,6 +134,9 @@ export default {
 		this.getTree()
 	},
 	methods: {
+		resetForm(formName) {
+			
+		},
 		treehandleSubmit() {
 			this.$refs.form.$refs['form'].validate((valid) => {
 				if (valid) {
@@ -153,7 +156,9 @@ export default {
 				}
 			})
 		},
-		handleNodeClick() {},
+		handleNodeClick(row) {
+			this.$emit('treeClick',row);
+		},
 		getTree() {
 			this.loading = true
 			API.videoClassifyTree(this.treeQuery).then(
@@ -168,8 +173,18 @@ export default {
 				}
 			)
 		},
-		add() {},
+		add(row) {
+			console.log(this.$refs.form)
+			this.treeParams = {
+				type: null,
+				parentId: null,
+				name: null,
+			}
+			this.treeParams.parentId = row.id
+			this.treeModal = true
+		},
 		treeDelete(row) {
+			console.log(row)
 			this.$confirm(this.$t('askDeleteData'), {
 				confirmButtonText: this.$t('submitText'),
 				cancelButtonText: this.$t('cancelText'),

+ 242 - 0
src/views/process/generalProcess/index.vue

@@ -0,0 +1,242 @@
+<template>
+  <div class="video-setting">
+    <el-row>
+      <el-col :span="24">
+        <el-card class="box-card">
+          <div slot="header">
+              <span>{{$t('process.processList')}}</span>
+          </div>
+          <test ref="req" v-model="req" :form-config="queryForm"></test>
+      
+          <el-table :data="tableList" v-loading="loading">
+            <el-table-column :label="$t('process.generalProcess.serviceName')" align="center" prop="serviceName" />
+            <el-table-column :label="$t('process.generalProcess.moduleName')" align="center" prop="moduleName" />
+            <el-table-column :label="$t('process.generalProcess.codeName')" align="center" prop="code" />
+            <!-- <el-table-column :label="$t('operation')" align="center" width="80">
+              <template slot-scope="scope">
+                <el-button type="text" @click="handleDelete(scope.row)">{{ $t('delete') }} </el-button>
+              </template>
+            </el-table-column> -->
+          </el-table>
+          <pagination v-show="total > 0" :total="total" :page.sync="req.pageNum" :limit.sync="req.pageSize" @pagination="getList" />
+          <el-dialog :title="$t('process.generalProcess.add')" :visible.sync="open" width="30%">
+            <from-render ref="form" v-model="dialogParams" :form-config="dialogForm" :insideRules="dialogRules"></from-render>
+          </el-dialog>
+        </el-card>
+      </el-col>
+    </el-row>
+  </div>
+</template>
+<script>
+import * as API from '@/api/process/generalProcess.js'
+import fromRender from '@/components/form-test/index.vue'
+
+export default {
+  name: 'equipment-management',
+  components: { fromRender },
+  data() {
+    return {
+      
+      req: {
+        pageNum: 1,
+        pageSize: 10,
+        keyword: '',
+        serviceName: '',
+      },
+      
+      queryForm: {
+        keyword: {
+          label: this.$t('keyword'),
+          span: 4,
+        },
+        serviceName: {
+          span: 4,
+          label: this.$t('process.generalProcess.serviceName'),
+          type: 'select',
+          data: [],
+          keyName: 'id',
+          labelName: 'name',
+        },
+        operation: {
+          // 搜索按钮操作
+          query: () => {
+            this.req.pageNum = 1
+            this.getList()
+          },
+          // 重置按钮操作
+          reset: () => {
+            this.$refs.req.reset()
+            this.getList()
+          },
+        },
+        otherButton: {
+          list: [
+            {
+              name: this.$t('process.generalProcess.add'),
+              methodsText: 'add',
+              type: 'primary',
+              add: () => {
+                this.open = true
+              },
+            },
+          ],
+        },
+      },
+      tableList: [],
+      total: 0,
+      loading: false,
+      open: false,
+      title: '',
+      dialogParams: {
+        fileInfoList: '',
+        videoClassifyId: '',
+        title: '',
+        richText: '',
+      },
+      dialogForm: {
+        loadingStatus: false,
+        serviceName: {
+          label: this.$t('process.generalProcess.serviceName'),
+          type: 'select',
+          data: [],
+          keyName: 'id',
+          labelName: 'name',
+        },
+        moduleName: {
+          label: this.$t('process.generalProcess.moduleName'),
+          type: 'input',
+        },
+        flowName: {
+          label: this.$t('process.generalProcess.flowName'),
+          type: 'input',
+        },
+        code: {
+          label: this.$t('process.generalProcess.code'),
+          span: 2,
+        },
+        otherButton: {
+          align: 'center',
+          list: [
+            {
+              name: this.$t('cancelText'),
+              methodsText: 'cancel',
+              cancel: () => {
+                this.$refs.form.reset()
+                this.open = false
+              },
+            },
+            {
+              name: this.$t('submitText'),
+              methodsText: 'submit',
+              type: 'primary',
+              submit: () => {
+                this.handleSubmit()
+              },
+            },
+          ],
+        },
+      },
+      dialogRules: {
+        serviceName: [{ required: true, message: this.$t('process.generalProcess.serviceNameRules'), trigger: 'change' }],
+        code: [{ required: true, message: this.$t('process.generalProcess.codeRules'), trigger: 'blur' }],
+        moduleName: [{ required: true, message: this.$t('process.generalProcess.moduleNameRules'), trigger: 'blur' }],
+        flowName: [{ required: true, message: this.$t('process.generalProcess.flowNameRules'), trigger: 'blur' }],
+      },
+    }
+  },
+  created() {
+    //this.getSelectList()
+  },
+  mounted() {
+    this.getList()
+    this.getServiceNameList()
+  },
+  watch: {
+    
+  },
+  methods: {
+    getServiceNameList(){
+      API.getServiceNameList({}).then(
+        (res) => {
+          const listData = []
+          for (let i = 0; i < res.data.data.length; i++) {
+            const element = res.data.data[i];
+            listData.push({
+              name:element,
+              id:element
+            })
+          }
+          this.dialogForm.serviceName.data = listData
+          this.queryForm.serviceName.data = listData
+        },
+        
+      )
+    },
+    reload(row){
+      console.log(row)
+      this.req.pageNum = 1
+      this.req.classifyId = row.id
+      this.getList()
+    },
+    getList() {
+      this.loading = true
+      API.page(this.req).then(
+        (res) => {
+          this.total = res.data.data.total
+          this.tableList = res.data.data.records
+          this.loading = false
+        },
+        (err) => {
+          console.log('tdaDevicePage: ' + err)
+          this.loading = false
+        },
+      )
+    },
+    handleSubmit() {
+      this.$refs.form.$refs['form'].validate((valid) => {
+        if (valid) {
+          this.dialogForm.loadingStatus = true
+          API.add(this.dialogParams).then(
+            () => {
+              this.msgSuccess(this.$t('addSuccess'))
+              this.$refs.form.reset()
+              this.open = false
+              this.dialogForm.loadingStatus = false
+              this.getList()
+            },
+            (err) => {
+              console.log('tdaDeviceAdd: ' + err)
+              this.dialogForm.loadingStatus = false
+            },
+          )
+          // } else {
+          //   setTimeout(() => {
+          //     const errorDiv = document.getElementsByClassName('is-error')
+          //     errorDiv[0].scrollIntoView()
+          //   }, 0)
+        }
+      })
+    },
+    handleDelete(row) {
+      this.$confirm(this.$t('askDeleteData'), {
+        confirmButtonText: this.$t('submitText'),
+        cancelButtonText: this.$t('cancelText'),
+        type: 'warning',
+      }).then(() => {
+        API.dlt({ id: row.id }).then(() => {
+          this.msgSuccess(this.$t('deleteSuccess'))
+          this.getList()
+        })
+      })
+    },
+  },
+}
+</script>
+
+<style lang="scss" scoped>
+
+.box-card {
+  height: calc(100vh - 110px);
+  overflow-y: auto;
+}
+</style>

+ 195 - 0
src/views/process/processNode/components/node-item.vue

@@ -0,0 +1,195 @@
+<template>
+  <div class="node-item" ref="node"
+    :class="[(isActive || isSelected) ? 'active' : '']"
+    :style="flowNodeContainer"
+    v-click-outside="setNotActive"
+    @click="setActive"
+    @mouseenter="showAnchor"
+    @mouseleave="hideAnchor"
+    @dblclick.prevent="editNode"
+    @contextmenu.prevent="onContextmenu">
+    <div class="log-wrap">
+      <img :src="node.logImg" alt="">
+    </div>
+    <div class="nodeName">{{node.nodeName}}</div>
+      <!--连线用--//触发连线的区域-->
+      <div class="node-anchor anchor-top" v-show="mouseEnter"></div>
+      <div class="node-anchor anchor-right" v-show="mouseEnter"></div>
+      <div class="node-anchor anchor-bottom" v-show="mouseEnter"></div>
+      <div class="node-anchor anchor-left" v-show="mouseEnter"></div>
+  </div>
+</template>
+
+<script>
+import ClickOutside from 'vue-click-outside'
+export default {
+  name: "nodeItem",
+  props: {
+      node: Object
+  },
+  directives: {
+    ClickOutside
+  },
+  computed: {
+    // 节点容器样式
+    flowNodeContainer: {
+      get() {
+        return {
+          top: this.node.top,
+          left: this.node.left
+        };
+      }
+    }
+  },
+  data() {
+    return {
+      mouseEnter: false,
+      isActive: false,
+      isSelected: false
+    };
+  },
+  methods: {
+    showAnchor() {
+      this.mouseEnter = true
+    },
+    hideAnchor() {
+      this.mouseEnter = false
+    },
+    onContextmenu() {
+      this.$contextmenu({
+        items: [{
+          label: '删除',
+          disabled: false,
+          icon: "",
+          onClick: () => {
+            this.deleteNode()
+          }
+        }],
+        event,
+        customClass: 'custom-class',
+        zIndex: 9999,
+        minWidth: 180
+      })
+    },
+    setActive() {
+      if(window.event.ctrlKey){
+        this.isSelected = !this.isSelected
+        return false
+      }
+      this.isActive = true
+      this.isSelected = false
+      setTimeout(() => {
+        this.$emit("changeLineState", this.node.id, true)
+      },0)
+    },
+    setNotActive() {
+      if(!window.event.ctrlKey){
+        this.isSelected = false
+      }
+      if(!this.isActive) {
+        return
+      }
+      this.$emit("changeLineState", this.node.id, false)
+      this.isActive = false
+    },
+    editNode() {
+      this.newNodeName = this.node.nodeName
+      this.$Modal.confirm({
+        render: (h) => {
+          return h('Input', {
+              props: {
+                value: this.newNodeName,
+                autofocus: true
+              },
+              on: {
+                input: (val) => {
+                  this.newNodeName = val;
+                }
+              }
+          })
+        },
+        onOk: () => {
+          console.log(this.newNodeName)
+          this.$emit('setNodeName', this.node.id, this.newNodeName)
+        }
+      })
+    },
+    deleteNode() {
+      this.$emit("deleteNode", this.node)
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+$labelColor: #409eff;
+
+$viewSize: 10px;
+.node-item {
+  position: absolute;
+  display: flex;
+  height: 40px;
+  width: 120px;
+  justify-content: center;
+  align-items: center;
+  border: 1px solid #b7b6b6;
+  border-radius: 4px;
+  cursor: move;
+  box-sizing: content-box;
+  z-index: 9995;
+  &:hover {
+    z-index: 9998;
+    .delete-btn{
+      display: block;
+    }
+  }
+  .log-wrap{
+    width: 40px;
+    height: 40px;
+    border-right: 1px solid  #b7b6b6;
+  }
+  .nodeName {
+    flex-grow: 1;
+    width: 0;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+  .node-anchor {
+    display: flex;
+    position: absolute;
+    width: 20px;
+    height: 20px;
+    align-items: center;
+    justify-content: center;
+    border-radius: 10px;
+    cursor: crosshair;
+    z-index: 9999;
+    background: -webkit-radial-gradient(sandybrown 10%, white 30%, #9a54ff 60%);
+  }
+  .anchor-top{
+    top: calc((20px / 2)*-1);
+    left: 50%;
+    margin-left: calc((20px/2)*-1);
+  }
+  .anchor-right{
+    top: 50%;
+    right: calc((20px / 2)*-1);
+    margin-top: calc((20px / 2)*-1);
+  }
+  .anchor-bottom{
+    bottom: calc((20px / 2)*-1);
+    left: 50%;
+    margin-left: calc((20px / 2)*-1);
+  }
+  .anchor-left{
+    top: 50%;
+    left: calc((20px / 2)*-1);
+    margin-top: calc((20px / 2)*-1);
+  }
+}
+.active{
+  border: 1px dashed $labelColor;
+  box-shadow: 0px 5px 9px 0px rgba(0,0,0,0.5);
+}
+</style>

+ 89 - 0
src/views/process/processNode/config/commonConfig.js

@@ -0,0 +1,89 @@
+export const jsplumbSetting = {
+  grid: [10, 10],
+  // 动态锚点、位置自适应
+  Anchors: [
+    "TopCenter",
+    "RightMiddle",
+    "BottomCenter",
+    "LeftMiddle"
+  ],
+  Container: "flow",
+  // 连线的样式 StateMachine、Flowchart,有四种默认类型:Bezier(贝塞尔曲线),Straight(直线),Flowchart(流程图),State machine(状态机)
+  Connector: ["Flowchart", { cornerRadius: 5, alwaysRespectStubs: true, stub: 5 }],
+  // 鼠标不能拖动删除线
+  ConnectionsDetachable: false,
+  // 删除线的时候节点不删除
+  DeleteEndpointsOnDetach: false,
+  // 连线的端点
+  // Endpoint: ["Dot", {radius: 5}],
+  Endpoint: [
+    "Rectangle",
+    {
+      height: 10,
+      width: 10
+    }
+  ],
+  // 线端点的样式
+  EndpointStyle: {
+    fill: "rgba(255,255,255,0)",
+    outlineWidth: 1
+  },
+  LogEnabled: false, //是否打开jsPlumb的内部日志记录
+  // 绘制线
+  PaintStyle: {
+    stroke: "#409eff",
+    strokeWidth: 2
+  },
+  HoverPaintStyle: { stroke: "#ff00cc", strokeWidth: 2 },
+  // 绘制箭头
+  Overlays: [
+    [
+      "Arrow",
+      {
+        width: 8,
+        length: 8,
+        location: 1
+      }
+    ]
+  ],
+  RenderMode: "svg"
+}
+
+// jsplumb连接参数
+export const jsplumbConnectOptions = {
+  isSource: true,
+  isTarget: true,
+  // 动态锚点、提供了4个方向 Continuous、AutoDefault
+  anchor: [
+    "TopCenter",
+    "RightMiddle",
+    "BottomCenter",
+    "LeftMiddle"
+  ]
+}
+
+export const jsplumbSourceOptions = {
+  filter: ".node-anchor", //触发连线的区域
+  /*"span"表示标签,".className"表示类,"#id"表示元素id*/
+  filterExclude: false,
+  anchor: [
+    "TopCenter",
+    "RightMiddle",
+    "BottomCenter",
+    "LeftMiddle"
+  ],
+  allowLoopback: false
+}
+
+export const jsplumbTargetOptions = {
+  filter: ".node-anchor",
+  /*"span"表示标签,".className"表示类,"#id"表示元素id*/
+  filterExclude: false,
+  anchor: [
+    "TopCenter",
+    "RightMiddle",
+    "BottomCenter",
+    "LeftMiddle"
+  ],
+  allowLoopback: false
+}

+ 4 - 0
src/views/process/processNode/config/data.json

@@ -0,0 +1,4 @@
+{
+  "nodeList": [{"type":"start","typeName":"开始","nodeName":"开始","id":"34v56ha2l9c000","top":"160px","left":"100px"},{"type":"dataSet","typeName":"文件","nodeName":"文件","id":"5sdjugrcqhc000","top":"160px","left":"315px"},{"type":"encode","typeName":"加密","nodeName":"加密","id":"3atqi5p6oa4000","top":"80px","left":"600px"},{"type":"personService","typeName":"个人服务","nodeName":"个人服务","id":"49vcu89p5q0000","top":"245px","left":"600px"},{"type":"arrange","typeName":"清洗","nodeName":"清洗","id":"1jhiilb0t2tc00","top":"180px","left":"880px"},{"type":"end","typeName":"结束","nodeName":"结束","id":"1ogr3wzy6zhc00","top":"180px","left":"1160px"}],
+  "lineList": [{"from":"34v56ha2l9c000","to":"5sdjugrcqhc000","label":"连线名称","id":"5n6pp5xqd6s000","Remark":""},{"from":"5sdjugrcqhc000","to":"3atqi5p6oa4000","label":"连线名称","id":"2a0ya9j1kev400","Remark":""},{"from":"5sdjugrcqhc000","to":"49vcu89p5q0000","label":"连线名称","id":"zoisvo5gpvk00","Remark":""},{"from":"3atqi5p6oa4000","to":"1jhiilb0t2tc00","label":"连线名称","id":"4xkb3dju1g0000","Remark":""},{"from":"49vcu89p5q0000","to":"1jhiilb0t2tc00","label":"连线名称","id":"ldc917l47w000","Remark":""},{"from":"1jhiilb0t2tc00","to":"1ogr3wzy6zhc00","label":"连线名称","id":"478galw3u34000","Remark":""}]
+}

+ 41 - 0
src/views/process/processNode/config/init.js

@@ -0,0 +1,41 @@
+const nodeTypeList = [{
+  type: 'start',
+  typeName: '开始',
+  nodeName: '开始',
+  
+  log_bg_color: 'rgba(0, 128, 0, 0.2)'
+},{
+  type: 'end',
+  typeName: '结束',
+  nodeName: '结束',
+
+  log_bg_color: 'rgba(255, 0, 0, 0.2)'
+},{
+  type: 'dataSet',
+  typeName: '文件',
+  nodeName: '文件',
+
+  log_bg_color: 'rgba(0, 128, 0, 0.2)'
+},{
+  type: 'encode',
+  typeName: '加密',
+  nodeName: '加密',
+
+  log_bg_color: 'rgba(163, 117, 233, 0.2)'
+},{
+  type: 'personService',
+  typeName: '个人服务',
+  nodeName: '个人服务',
+
+  log_bg_color: 'rgba(132, 166, 251, 0.2)'
+},{
+  type: 'arrange',
+  typeName: '清洗',
+  nodeName: '清洗',
+
+  log_bg_color: 'rgba(250, 205, 81, 0.2)'
+}]
+
+console.log(nodeTypeList)
+
+export {nodeTypeList};

+ 316 - 0
src/views/process/processNode/config/methods.js

@@ -0,0 +1,316 @@
+import panzoom from "panzoom";
+import { GenNonDuplicateID } from "@/util/util";
+
+const methods = {
+  init() {
+    this.jsPlumb.ready(() => {
+      // 导入默认配置
+      this.jsPlumb.importDefaults(this.jsplumbSetting);
+      //完成连线前的校验
+      this.jsPlumb.bind("beforeDrop", evt => {
+        let res = () => { } //此处可以添加是否创建连接的校验, 返回 false 则不添加; 
+        return res
+      })
+      // 连线创建成功后,维护本地数据
+      this.jsPlumb.bind("connection", evt => {
+        this.addLine(evt)
+      });
+      //连线双击删除事件
+      this.jsPlumb.bind("dblclick",(conn, originalEvent) => {
+        this.confirmDelLine(conn)
+      })
+      //断开连线后,维护本地数据
+      this.jsPlumb.bind("connectionDetached", evt => {
+        this.deleLine(evt)
+      })
+      this.loadEasyFlow();
+      // 会使整个jsPlumb立即重绘。
+      this.jsPlumb.setSuspendDrawing(false, true);
+    });
+    this.initPanZoom();
+  },
+  // 加载流程图
+  loadEasyFlow() {
+    // 初始化节点
+    for (let i = 0; i < this.data.nodeList.length; i++) {
+      let node = this.data.nodeList[i];
+      // 设置源点,可以拖出线连接其他节点
+      this.jsPlumb.makeSource(node.id, this.jsplumbSourceOptions);
+      // // 设置目标点,其他源点拖出的线可以连接该节点
+      this.jsPlumb.makeTarget(node.id, this.jsplumbTargetOptions);
+      // this.jsPlumb.draggable(node.id);
+      this.draggableNode(node.id)
+    }
+
+    // 初始化连线
+    this.jsPlumb.unbind("connection"); //取消连接事件
+    for (let i = 0; i < this.data.lineList.length; i++) {
+      let line = this.data.lineList[i];
+      this.jsPlumb.connect(
+        {
+          source: line.from,
+          target: line.to
+        },
+        this.jsplumbConnectOptions
+      );
+    }
+    this.jsPlumb.bind("connection", evt => {
+      let from = evt.source.id;
+      let to = evt.target.id;
+      this.data.lineList.push({
+        from: from,
+        to: to,
+        label: "连线名称",
+        id: GenNonDuplicateID(8),
+        Remark: ""
+      });
+    });
+  },
+  draggableNode(nodeId) {
+    this.jsPlumb.draggable(nodeId, {
+      grid: this.commonGrid,
+      drag: (params) => {
+        this.alignForLine(nodeId, params.pos)
+      },
+      start: () => {
+
+      },
+      stop: (params) => {
+        this.auxiliaryLine.isShowXLine = false
+        this.auxiliaryLine.isShowYLine = false
+        this.changeNodePosition(nodeId, params.pos)
+      }
+    })
+  },
+  //移动节点时,动态显示对齐线
+  alignForLine(nodeId, position) {
+    let showXLine = false, showYLine = false
+    this.data.nodeList.some(el => {
+      if(el.id !== nodeId && el.left == position[0]+'px') {
+        this.auxiliaryLinePos.x = position[0] + 60;
+        showYLine = true
+      }
+      if(el.id !== nodeId && el.top == position[1]+'px') {
+        this.auxiliaryLinePos.y = position[1] + 20;
+        showXLine = true
+      }
+    })
+    this.auxiliaryLine.isShowYLine = showYLine
+    this.auxiliaryLine.isShowXLine = showXLine
+  },
+  changeNodePosition(nodeId, pos) {
+    this.data.nodeList.some(v => {
+      if(nodeId == v.id) {
+        v.left = pos[0] +'px'
+        v.top = pos[1] + 'px'
+        return true
+      }else {
+        return false
+      }
+    })
+  },
+  drag(ele, item) {
+    this.currentItem = item;
+  },
+  drop(event) {
+    const containerRect = this.jsPlumb.getContainer().getBoundingClientRect();
+    const scale = this.getScale();
+    let left = (event.pageX - containerRect.left -60) / scale;
+    let top = (event.pageY - containerRect.top -20) / scale;
+
+    var temp = {
+      ...this.currentItem,
+      id: GenNonDuplicateID(8),
+      top: (Math.round(top/20))*20 + "px",
+      left:  (Math.round(left/20))*20 + "px"
+    };
+    this.addNode(temp);
+  },
+  addLine(line) {
+    let from = line.source.id;
+    let to = line.target.id;
+    this.data.lineList.push({
+      from: from,
+      to: to,
+      label: "连线名称",
+      id: GenNonDuplicateID(8),
+      Remark: ""
+    });
+  },
+  confirmDelLine(line) {
+    this.$Modal.confirm({
+      title: '删除连线',
+      content: "<p>确认删除该连线?</p>",
+      onOk: () => {
+        this.jsPlumb.deleteConnection(line)
+      }
+    })
+  },
+  deleLine(line) {
+    this.data.lineList.forEach((item, index) => {
+      if(item.from === line.sourceId && item.to === line.targetId) {
+        this.data.lineList.splice(index, 1)
+      }
+    })
+  },
+  // dragover默认事件就是不触发drag事件,取消默认事件后,才会触发drag事件
+  allowDrop(event) {
+    event.preventDefault();
+  },
+  getScale() {
+    let scale1;
+    if (this.jsPlumb.pan) {
+      const { scale } = this.jsPlumb.pan.getTransform();
+      scale1 = scale;
+    } else {
+      const matrix = window.getComputedStyle(this.jsPlumb.getContainer()).transform;
+      scale1 = matrix.split(", ")[3] * 1;
+    }
+    this.jsPlumb.setZoom(scale1);
+    return scale1;
+  },
+  // 添加新的节点
+  addNode(temp) {
+    this.data.nodeList.push(temp);
+    this.$nextTick(() => {
+      this.jsPlumb.makeSource(temp.id, this.jsplumbSourceOptions);
+      this.jsPlumb.makeTarget(temp.id, this.jsplumbTargetOptions);
+      this.draggableNode(temp.id)
+    });
+  },
+
+  initPanZoom() {
+    const mainContainer = this.jsPlumb.getContainer();
+    const mainContainerWrap = mainContainer.parentNode;
+    const pan = panzoom(mainContainer, {
+      smoothScroll: false,
+      bounds: true,
+      // autocenter: true,
+      zoomDoubleClickSpeed: 1,
+      minZoom: 0.5,
+      maxZoom: 2,
+      //设置滚动缩放的组合键,默认不需要组合键
+      beforeWheel: (e) => {
+        console.log(e)
+        // let shouldIgnore = !e.ctrlKey
+        // return shouldIgnore
+      },
+      beforeMouseDown: function(e) {
+        // allow mouse-down panning only if altKey is down. Otherwise - ignore
+        var shouldIgnore = e.ctrlKey;
+        return shouldIgnore;
+      }
+    });
+    this.jsPlumb.mainContainerWrap = mainContainerWrap;
+    this.jsPlumb.pan = pan;
+    // 缩放时设置jsPlumb的缩放比率
+    pan.on("zoom", e => {
+      const { x, y, scale } = e.getTransform();
+      this.jsPlumb.setZoom(scale);
+      //根据缩放比例,缩放对齐辅助线长度和位置
+      this.auxiliaryLinePos.width = (1/scale) * 100 + '%'
+      this.auxiliaryLinePos.height = (1/scale) * 100 + '%'
+      this.auxiliaryLinePos.offsetX = -(x/scale)
+      this.auxiliaryLinePos.offsetY = -(y/scale)
+    });
+    pan.on("panend", (e) => {
+      const {x, y, scale} = e.getTransform();
+      this.auxiliaryLinePos.width = (1/scale) * 100 + '%'
+      this.auxiliaryLinePos.height = (1/scale) * 100 + '%'
+      this.auxiliaryLinePos.offsetX = -(x/scale)
+      this.auxiliaryLinePos.offsetY = -(y/scale)
+    })
+
+    // 平移时设置鼠标样式
+    mainContainerWrap.style.cursor = "grab";
+    mainContainerWrap.addEventListener("mousedown", function wrapMousedown() {
+      this.style.cursor = "grabbing";
+      mainContainerWrap.addEventListener("mouseout", function wrapMouseout() {
+        this.style.cursor = "grab";
+      });
+    });
+    mainContainerWrap.addEventListener("mouseup", function wrapMouseup() {
+      this.style.cursor = "grab";
+    });
+  }, 
+
+  setNodeName(nodeId, name) {
+    this.data.nodeList.some((v) => {
+      if(v.id === nodeId) {
+        v.nodeName = name
+        return true
+      }else {
+        return false
+      }
+    })
+  },
+
+  //删除节点
+  deleteNode(node) {
+    this.data.nodeList.some((v,index) => {
+      if(v.id === node.id) {
+        this.data.nodeList.splice(index, 1)
+        this.jsPlumb.remove(v.id)
+        return true
+      }else {
+        return false
+      }
+    })
+  },
+
+  //更改连线状态
+  changeLineState(nodeId, val) {
+    console.log(val)
+    let lines = this.jsPlumb.getAllConnections()
+    lines.forEach(line => {
+      if(line.targetId === nodeId || line.sourceId === nodeId) {
+        if(val) {
+          line.canvas.classList.add('active')
+        }else {
+          line.canvas.classList.remove('active')
+        }
+      }
+    })
+  },
+
+  //初始化节点位置  (以便对齐,居中)
+  fixNodesPosition() {
+    if(this.data.nodeList && this.$refs.flowWrap) {
+      const nodeWidth = 120
+      const nodeHeight = 40
+      let wrapInfo = this.$refs.flowWrap.getBoundingClientRect()
+      let maxLeft = 0, minLeft = wrapInfo.width, maxTop = 0, minTop = wrapInfo.height;
+      let nodePoint = {
+        left: 0,
+        right: 0,
+        top: 0,
+        bottom: 0
+      }
+      let fixTop = 0, fixLeft = 0;
+      this.data.nodeList.forEach(el => {
+        let top = Number(el.top.substring(0, el.top.length -2))
+        let left = Number(el.left.substring(0, el.left.length -2))
+        maxLeft = left > maxLeft ? left : maxLeft
+        minLeft = left < minLeft ? left : minLeft
+        maxTop = top > maxTop ? top : maxTop
+        minTop = top < minTop ? top : minTop
+      })
+      nodePoint.left = minLeft
+      nodePoint.right = wrapInfo.width - maxLeft - nodeWidth
+      nodePoint.top = minTop
+      nodePoint.bottom = wrapInfo.height - maxTop - nodeHeight;
+
+      fixTop = nodePoint.top !== nodePoint.bottom ? (nodePoint.bottom - nodePoint.top) / 2 : 0;
+      fixLeft = nodePoint.left !== nodePoint.right ? (nodePoint.right - nodePoint.left) / 2 : 0;
+
+      this.data.nodeList.map(el => {
+        let top = Number(el.top.substring(0, el.top.length - 2)) + fixTop;
+        let left = Number(el.left.substring(0, el.left.length - 2)) + fixLeft;
+        el.top = (Math.round(top/20))* 20 + 'px'
+        el.left = (Math.round(left/20))*20 + 'px'
+      })
+    }
+  }, 
+}
+
+export default methods;

+ 242 - 0
src/views/process/processNode/index.vue

@@ -0,0 +1,242 @@
+<template>
+    <div class="video-setting">
+      <el-row>
+        <el-col :span="24">
+          <el-card class="box-card">
+            <div slot="header">
+                <span>{{$t('process.processList')}}</span>
+            </div>
+            <from-render ref="req" v-model="req" :form-config="queryForm"></from-render>
+        
+            <el-table :data="tableList" v-loading="loading">
+              <el-table-column :label="$t('process.generalProcess.serviceName')" align="center" prop="serviceName" />
+              <el-table-column :label="$t('process.generalProcess.moduleName')" align="center" prop="moduleName" />
+              <el-table-column :label="$t('process.generalProcess.codeName')" align="center" prop="code" />
+              <!-- <el-table-column :label="$t('operation')" align="center" width="80">
+                <template slot-scope="scope">
+                  <el-button type="text" @click="handleDelete(scope.row)">{{ $t('delete') }} </el-button>
+                </template>
+              </el-table-column> -->
+            </el-table>
+            <pagination v-show="total > 0" :total="total" :page.sync="req.pageNum" :limit.sync="req.pageSize" @pagination="getList" />
+            <el-dialog :title="$t('process.generalProcess.add')" :visible.sync="open" width="30%">
+              <from-render ref="form" v-model="dialogParams" :form-config="dialogForm" :insideRules="dialogRules"></from-render>
+            </el-dialog>
+          </el-card>
+        </el-col>
+      </el-row>
+    </div>
+  </template>
+  <script>
+  import * as API from '@/api/process/generalProcess.js'
+  import fromRender from '@/components/form-test/index.vue'
+
+  export default {
+    name: 'equipment-management',
+    components: { fromRender },
+    data() {
+      return {
+        
+        req: {
+          pageNum: 1,
+          pageSize: 10,
+          keyword: '',
+          serviceName: '',
+        },
+        
+        queryForm: {
+          keyword: {
+            label: this.$t('keyword'),
+            span: 4,
+          },
+          serviceName: {
+            span: 4,
+            label: this.$t('process.generalProcess.serviceName'),
+            type: 'select',
+            data: [],
+            keyName: 'id',
+            labelName: 'name',
+          },
+          operation: {
+            // 搜索按钮操作
+            query: () => {
+              this.req.pageNum = 1
+              this.getList()
+            },
+            // 重置按钮操作
+            reset: () => {
+              this.$refs.req.reset()
+              this.getList()
+            },
+          },
+          otherButton: {
+            list: [
+              {
+                name: this.$t('process.generalProcess.add'),
+                methodsText: 'add',
+                type: 'primary',
+                add: () => {
+                  this.open = true
+                },
+              },
+            ],
+          },
+        },
+        tableList: [],
+        total: 0,
+        loading: false,
+        open: false,
+        title: '',
+        dialogParams: {
+          fileInfoList: '',
+          videoClassifyId: '',
+          title: '',
+          richText: '',
+        },
+        dialogForm: {
+          loadingStatus: false,
+          serviceName: {
+            label: this.$t('process.generalProcess.serviceName'),
+            type: 'select',
+            data: [],
+            keyName: 'id',
+            labelName: 'name',
+          },
+          moduleName: {
+            label: this.$t('process.generalProcess.moduleName'),
+            type: 'input',
+          },
+          flowName: {
+            label: this.$t('process.generalProcess.flowName'),
+            type: 'input',
+          },
+          code: {
+            label: this.$t('process.generalProcess.code'),
+            span: 2,
+          },
+          otherButton: {
+            align: 'center',
+            list: [
+              {
+                name: this.$t('cancelText'),
+                methodsText: 'cancel',
+                cancel: () => {
+                  this.$refs.form.reset()
+                  this.open = false
+                },
+              },
+              {
+                name: this.$t('submitText'),
+                methodsText: 'submit',
+                type: 'primary',
+                submit: () => {
+                  this.handleSubmit()
+                },
+              },
+            ],
+          },
+        },
+        dialogRules: {
+          serviceName: [{ required: true, message: this.$t('process.generalProcess.serviceNameRules'), trigger: 'change' }],
+          code: [{ required: true, message: this.$t('process.generalProcess.codeRules'), trigger: 'blur' }],
+          moduleName: [{ required: true, message: this.$t('process.generalProcess.moduleNameRules'), trigger: 'blur' }],
+          flowName: [{ required: true, message: this.$t('process.generalProcess.flowNameRules'), trigger: 'blur' }],
+        },
+      }
+    },
+    created() {
+      //this.getSelectList()
+    },
+    mounted() {
+      this.getList()
+      this.getServiceNameList()
+    },
+    watch: {
+      
+    },
+    methods: {
+      getServiceNameList(){
+        API.getServiceNameList({}).then(
+          (res) => {
+            const listData = []
+            for (let i = 0; i < res.data.data.length; i++) {
+              const element = res.data.data[i];
+              listData.push({
+                name:element,
+                id:element
+              })
+            }
+            this.dialogForm.serviceName.data = listData
+            this.queryForm.serviceName.data = listData
+          },
+          
+        )
+      },
+      reload(row){
+        console.log(row)
+        this.req.pageNum = 1
+        this.req.classifyId = row.id
+        this.getList()
+      },
+      getList() {
+        this.loading = true
+        API.page(this.req).then(
+          (res) => {
+            this.total = res.data.data.total
+            this.tableList = res.data.data.records
+            this.loading = false
+          },
+          (err) => {
+            console.log('tdaDevicePage: ' + err)
+            this.loading = false
+          },
+        )
+      },
+      handleSubmit() {
+        this.$refs.form.$refs['form'].validate((valid) => {
+          if (valid) {
+            this.dialogForm.loadingStatus = true
+            API.add(this.dialogParams).then(
+              () => {
+                this.msgSuccess(this.$t('addSuccess'))
+                this.$refs.form.reset()
+                this.open = false
+                this.dialogForm.loadingStatus = false
+                this.getList()
+              },
+              (err) => {
+                console.log('tdaDeviceAdd: ' + err)
+                this.dialogForm.loadingStatus = false
+              },
+            )
+            // } else {
+            //   setTimeout(() => {
+            //     const errorDiv = document.getElementsByClassName('is-error')
+            //     errorDiv[0].scrollIntoView()
+            //   }, 0)
+          }
+        })
+      },
+      handleDelete(row) {
+        this.$confirm(this.$t('askDeleteData'), {
+          confirmButtonText: this.$t('submitText'),
+          cancelButtonText: this.$t('cancelText'),
+          type: 'warning',
+        }).then(() => {
+          API.dlt({ id: row.id }).then(() => {
+            this.msgSuccess(this.$t('deleteSuccess'))
+            this.getList()
+          })
+        })
+      },
+    },
+  }
+  </script>
+  
+  <style lang="scss" scoped>
+
+  .box-card {
+    height: calc(100vh - 110px);
+    overflow-y: auto;
+  }
+  </style>

+ 245 - 0
src/views/process/processNode/nodeConfig.vue

@@ -0,0 +1,245 @@
+
+
+<template>
+	<div class="video-setting flow_region">
+		<el-row :gutter="20">
+			<el-col :span="3">
+				<el-card shadow="always" :body-style="{ padding: '20px' }">
+					<div slot="header">
+						<span>节点列表</span>
+					</div>
+					<div class="nodes-wrap">
+						<div
+							v-for="item in nodeTypeList"
+							:key="item.type"
+							class="node"
+							draggable="true"
+							@dragstart="drag($event, item)"
+						>
+							<div class="name">{{ item.typeName }}</div>
+						</div>
+					</div>
+				</el-card>
+			</el-col>
+
+			<el-col :span="16">
+				<el-card class="box-card">
+					<div slot="header">
+						<span>{{ $t('process.processList') }}</span>
+					</div>
+					<div
+						id="flowWrap"
+						ref="flowWrap"
+						class="flow-wrap"
+						@drop="drop($event)"
+						@dragover="allowDrop($event)"
+					>
+						<div id="flow">
+							<div
+								v-show="auxiliaryLine.isShowXLine"
+								class="auxiliary-line-x"
+								:style="{
+									width: auxiliaryLinePos.width,
+									top: auxiliaryLinePos.y + 'px',
+									left: auxiliaryLinePos.offsetX + 'px',
+								}"
+							></div>
+							<div
+								v-show="auxiliaryLine.isShowYLine"
+								class="auxiliary-line-y"
+								:style="{
+									height: auxiliaryLinePos.height,
+									left: auxiliaryLinePos.x + 'px',
+									top: auxiliaryLinePos.offsetY + 'px',
+								}"
+							></div>
+							<flowNode
+								v-for="item in data.nodeList"
+								:id="item.id"
+								:key="item.id"
+								:node="item"
+								@setNodeName="setNodeName"
+								@deleteNode="deleteNode"
+								@changeLineState="changeLineState"
+							></flowNode>
+						</div>
+					</div>
+				</el-card>
+			</el-col>
+            <el-col :span="5">
+				<el-card shadow="always" :body-style="{ padding: '20px' }">
+					<div slot="header">
+						<span>节点设置</span>
+					</div>
+				</el-card>
+			</el-col>
+		</el-row>
+	</div>
+</template>
+  <script>
+import * as API from '@/api/process/generalProcess.js'
+import fromRender from '@/components/form-test/index.vue'
+import { jsPlumb } from 'jsplumb'
+import { nodeTypeList } from './config/init'
+import {
+	jsplumbSetting,
+	jsplumbConnectOptions,
+	jsplumbSourceOptions,
+	jsplumbTargetOptions,
+} from './config/commonConfig'
+import methods from './config/methods'
+import data from './config/data.json'
+import flowNode from './components/node-item'
+export default {
+	name: 'equipment-management',
+	components: { fromRender, flowNode },
+	data() {
+		return {
+			jsPlumb: null,
+			currentItem: null,
+			nodeTypeList: nodeTypeList,
+			nodeTypeObj: {},
+			data: {
+				nodeList: [],
+				lineList: [],
+			},
+			selectedList: [],
+			jsplumbSetting: jsplumbSetting,
+			jsplumbConnectOptions: jsplumbConnectOptions,
+			jsplumbSourceOptions: jsplumbSourceOptions,
+			jsplumbTargetOptions: jsplumbTargetOptions,
+			auxiliaryLine: { isShowXLine: false, isShowYLine: false }, //对齐辅助线是否显示
+			auxiliaryLinePos: {
+				width: '100%',
+				height: '100%',
+				offsetX: 0,
+				offsetY: 0,
+				x: 20,
+				y: 20,
+			},
+			commonGrid: [5, 5], //节点移动最小距离
+			selectModuleFlag: false, //多选标识
+			rectAngle: {
+				px: '', //多选框绘制时的起始点横坐标
+				py: '', //多选框绘制时的起始点纵坐标
+				left: 0,
+				top: 0,
+				height: 0,
+				width: 0,
+			},
+		}
+	},
+	created() {
+		//this.getSelectList()
+	},
+	mounted() {
+		this.jsPlumb = jsPlumb.getInstance()
+		this.initNodeTypeObj()
+		this.initNode()
+		this.fixNodesPosition()
+		this.$nextTick(() => {
+			this.init()
+		})
+	},
+	watch: {},
+	methods: {
+		...methods,
+		initNodeTypeObj() {
+			nodeTypeList.map((v) => {
+				this.nodeTypeObj[v.type] = v
+			})
+		},
+		initNode() {
+			this.data.lineList = data.lineList
+			data.nodeList.map((v) => {
+				v.logImg = this.nodeTypeObj[v.type].logImg
+				v.log_bg_color = this.nodeTypeObj[v.type].log_bg_color
+				this.data.nodeList.push(v)
+			})
+		},
+	},
+}
+</script>
+  
+<style lang="scss" scoped>
+
+.box-card {
+	height: calc(100vh - 110px);
+	overflow-y: auto;
+}
+.jtk-connector.active {
+	z-index: 9999;
+	path {
+		stroke: #150042;
+		stroke-width: 1.5;
+		animation: ring;
+		animation-duration: 3s;
+		animation-timing-function: linear;
+		animation-iteration-count: infinite;
+		stroke-dasharray: 5;
+	}
+}
+@keyframes ring {
+	from {
+		stroke-dashoffset: 50;
+	}
+	to {
+		stroke-dashoffset: 0;
+	}
+}
+</style>
+<style lang="scss" scoped>
+.flow_region {
+	.nodes-wrap {
+		width: 100%;
+		height: 100%;
+        text-align: center;
+		.node {
+			display: flex;
+			height: 40px;
+			width: 100%;
+			margin: 5px auto;
+			border: 1px solid #ccc;
+			line-height: 40px;
+			&:hover {
+				cursor: grab;
+			}
+			&:active {
+				cursor: grabbing;
+			}
+			.log {
+				width: 40px;
+				height: 40px;
+			}
+			.name {
+				width: 0;
+				flex-grow: 1;
+			}
+		}
+	}
+	.flow-wrap {
+		height: calc(100vh - 300px);
+		position: relative;
+		overflow: hidden;
+		outline: none !important;
+		flex-grow: 1;
+		// background-image: url('../assets/point.png');
+		#flow {
+			position: relative;
+			width: 100%;
+			height: 100%;
+			.auxiliary-line-x {
+				position: absolute;
+				border: 0.5px dashed #2ab1e8;
+				z-index: 9999;
+			}
+			.auxiliary-line-y {
+				position: absolute;
+				border: 0.5px dashed #2ab1e8;
+				z-index: 9999;
+			}
+		}
+	}
+}
+</style>
+