Browse Source

根节点参数

CzRger 2 months ago
parent
commit
67875eaedb

File diff suppressed because it is too large
+ 8 - 0
src/assets/svg/vars.svg


+ 1 - 0
src/components/czr-ui/CzrButton.vue

@@ -42,6 +42,7 @@ const props = defineProps({
 
 <style lang="scss" scoped>
 .czr-button {
+  width: fit-content;
   height: 28px;
   background: rgba(var(--czr-main-color-rgb),0.1);
   border-radius: 2px;

+ 4 - 2
src/components/czr-ui/CzrForm.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="czr-form">
-    <el-form ref="ref_czrForm" :label-width="labelWidth" v-bind="$attrs" @submit.native.prevent>
+    <el-form ref="ref_czrForm" :label-width="labelWidth" v-bind="$attrs" @submit.native.prevent :label-position="layout === 'y' ? 'top' : 'right'">
       <el-row style="display:flex; flex-wrap: wrap">
         <slot/>
       </el-row>
@@ -22,7 +22,8 @@ const emit = defineEmits(['handleEnter'])
 const props = defineProps({
   labelWidth: {default: 'auto'},
   elementLoadingBackground: {default: null},
-  formView: {default: false}
+  formView: {default: false},
+  layout: {default: 'x'}
 })
 const state = reactive({
   formChildrenMap: new Map()
@@ -35,6 +36,7 @@ watch(() => props.formView, (n) => {
   _formView.value = n
 })
 provide('form-view', _formView)
+provide('form-layout', props.layout)
 const submit = () => {
   return new Promise((resolve, reject) => {
     const nodes: any = [];

+ 14 - 2
src/components/czr-ui/CzrFormColumn.vue

@@ -10,7 +10,8 @@
       ['link_' + link + ($attrs.type ? '_' + $attrs.type : '')]: true,
       required: required !== false,
       'no-label': (labelWidth == 0 || labelWidth === '0px') && !isValue(label) && !$slots.label,
-      'is-error': !!state.errorMessage
+      'is-error': !!state.errorMessage,
+      [`layout-${layout || formLayout}`]: true
     }" :error="state.errorMessage" :required="required">
       <template #label>
         <slot name="label"/>
@@ -251,7 +252,8 @@ const props = defineProps({
   minLength: {type: Number, default: null}, // 最小长度
   defaultErrorMsg: {default: null}, // 默认校验错误提示
   unit: {default: '', type: String},  // 单位
-  otherInfo: {} // 其他信息
+  otherInfo: {}, // 其他信息
+  layout: {}
 })
 const attrs = (getCurrentInstance() as ComponentInternalInstance).attrs
 const state = reactive({
@@ -307,6 +309,7 @@ const handleValidate = (val: any = undefined, val2: any = undefined) => {
   }
   return state.errorMessage
 }
+const formLayout = inject('form-layout')
 const handleEnterFunc = inject('handle-enter', () => {})
 const handleEnter = () => {
   handleEnterFunc?.()
@@ -391,6 +394,15 @@ defineExpose({
         display: none;
       }
     }
+    &.layout-y {
+      .el-form-item__label {
+        justify-content: flex-start;
+        padding-left: 0;
+      }
+    }
+    &.layout-x {
+      display: flex;
+    }
     $textColor: #576275;
     .el-form-item__label {
       line-height: 1;

+ 2 - 2
src/style/czr.scss

@@ -1,6 +1,6 @@
 :root {
-  --czr-main-color: rgba(0, 194, 124, 1);
-  --czr-main-color-rgb: 0, 194, 124;
+  --czr-main-color: rgba(21, 90, 239, 1);
+  --czr-main-color-rgb: 21, 90, 239;
 }
 
 .__hover {

+ 0 - 2
src/style/index.scss

@@ -5,8 +5,6 @@
   outline: none;  // dom元素选中带边框
   -webkit-user-drag: none;
   box-sizing: border-box;
-  margin: 0;
-  padding: 0;
   font-family: "PingFang SC";
 }
 

+ 5 - 2
src/views/workflow/chart/index.vue

@@ -10,7 +10,7 @@
 import {createVNode, getCurrentInstance, inject, nextTick, onMounted, provide, reactive, ref, render, watch} from "vue";
 import {Graph, Markup, Shape} from '@antv/x6'
 import {WorkflowFunc} from "@/views/workflow/types";
-import {lineStyle} from "@/views/workflow/config";
+import {getNodeDefault, lineStyle} from "@/views/workflow/config";
 import nodeAdd from './node-add.vue'
 import {register} from "@antv/x6-vue-shape";
 import WorkflowNode from "./node-index.vue";
@@ -161,7 +161,10 @@ const initNodes = () => {
   props.data.nodes.forEach(v => {
     state.graph.addNode(handleNode(v, state.graph))
   })
-
+  if (props.data.nodes.length === 0) {
+    const node = getNodeDefault('root')
+    state.graph.addNode(handleNode(node, state.graph))
+  }
 }
 const initEdges = () => {
   if (!state.isInitEdges) {

+ 1 - 0
src/views/workflow/chart/node-add.vue

@@ -20,6 +20,7 @@
       </template>
       <div class="node-add">
         <div class="node-add-item __hover" @click="onAddNode('test')">测试节点</div>
+        <div class="node-add-item __hover" @click="onAddNode('root')">根节点</div>
         <div class="node-add-item __hover" @click="onAddNode('if-else')">条件分支</div>
       </div>
     </ElPopover>

+ 18 - 29
src/views/workflow/config.ts

@@ -1,5 +1,6 @@
 import { v4 } from "uuid";
 import {NodeDataStruct, NodePortStruct, NodeStruct} from "@/views/workflow/types";
+import rootNodeDefault from "@/views/workflow/instance/root/default";
 
 export const lineStyle = {
   line: {
@@ -21,59 +22,47 @@ export const portStyle = {
 }
 
 export const nodeDefault = {
-  test: <NodeStruct>{
-    id: '',
+  test: () => (<NodeStruct>{
     x: 0,
     y: 0,
     data: <NodeDataStruct>{
-      id: '',
+      id: v4(),
       title: '测试节点',
       type: 'test',
     }
-  },
-  root: <NodeStruct>{
-    id: '',
+  }),
+  root: () => (<NodeStruct>{
     x: 0,
     y: 0,
     data: <NodeDataStruct>{
-      id: '',
+      id: v4(),
       title: '开始',
       type: 'root',
+      ...rootNodeDefault.defaultValue,
     }
-  },
-  'if-else': <NodeStruct>{
-    id: '',
+  }),
+  'if-else': () => (<NodeStruct>{
     x: 0,
     y: 0,
     data: <NodeDataStruct>{
-      id: '',
+      id: v4(),
       title: '条件分支',
       type: 'if-else',
       ports: <NodePortStruct[]>[
         {
-          id: '',
-          data: {
-            p1: ''
-          }
+          id: v4(),
+          data: {}
         },
         {
-          id: '',
-          data: {
-            p1: ''
-          }
+          id: v4(),
+          data: {}
         },
       ],
     }
-  }
+  })
 }
 export const getNodeDefault = (type) => {
-  const res = nodeDefault[type]
-  res.id = v4()
-  res.data.id = res.id
-  if (type === "if-else") {
-    res.data.ports.forEach(port => {
-      port.id = v4()
-    })
-  }
-  return res
+  const node = nodeDefault[type]()
+  node.id = node.data.id
+  return node
 }

+ 10 - 1
src/views/workflow/handle.ts

@@ -3,6 +3,11 @@ import {merge} from "lodash";
 import {lineStyle, portStyle} from "@/views/workflow/config";
 import { v4 } from "uuid";
 
+const systemVars = [
+  {key: 'sys.query', type: 'String'},
+  {key: 'sys.user_id', type: 'String'},
+]
+
 export const handleNode = (no, graph) => {
   const id = v4()
   if (!no.id) {
@@ -36,7 +41,11 @@ export const handleNode = (no, graph) => {
       }
     ]
   }
-  if (node.data.type !== 'root') {
+  if (node.data.type === 'root') {
+    node.data.sysVars = [
+      ...systemVars
+    ]
+  } else {
     node.ports.items.push({
       id: `${node.id}_start`,
       group: 'start',

+ 2 - 2
src/views/workflow/index.vue

@@ -14,7 +14,7 @@ import {getCurrentInstance, onMounted, provide, reactive, ref} from "vue";
 import workflowChart from './chart/index.vue'
 import workflowPanel from './chart/panel-index.vue'
 import { getTeleport } from '@antv/x6-vue-shape'
-import {data} from './mockJson'
+import {data1, data2} from './mockJson'
 import {WorkflowFunc} from "@/views/workflow/types";
 const TeleportContainer = getTeleport()
 
@@ -72,7 +72,7 @@ const getJsonData = () => {
   console.log(res)
 }
 const initData = () => {
-  state.workflowData = data
+  state.workflowData = data1
 }
 onMounted(() => {
   initData()

+ 133 - 0
src/views/workflow/instance/component/vars/vars-detail.vue

@@ -0,0 +1,133 @@
+<template>
+  <CzrDialog
+    :show="show"
+    :title="titleCpt"
+    @onClose="$emit('update:show', false)"
+    width="500px"
+    height="auto"
+    maxHeight="80%"
+    :loading="state.loading"
+    @onSubmit="onSubmit"
+  >
+    <div class="vars-detail">
+      <div class="vars-detail-types">
+        <template v-for="item in optionsType">
+          <div class="__hover" :class="{active: state.form.type === item.type}" @click="state.form.type = item.type">{{item.label}}</div>
+        </template>
+      </div>
+      <CzrForm ref="ref_form" class="vars-detail-form" layout="y">
+        <CzrFormColumn
+          :span="24"
+          required
+          label="显示名称"
+          v-model:param="state.form.label"
+        />
+        <CzrFormColumn
+          :span="24"
+          required
+          label="变量名称"
+          v-model:param="state.form.key"
+        />
+        <template v-if="state.form.type === 'String' || state.form.type === 'Textarea'">
+          <CzrFormColumn
+            :span="24"
+            required
+            label="最大长度"
+            v-model:param="state.form.length"
+          />
+        </template>
+      </CzrForm>
+    </div>
+  </CzrDialog>
+</template>
+
+<script setup lang="ts">
+import {computed, getCurrentInstance, nextTick, reactive, ref, watch} from "vue";
+import {ElMessage, ElMessageBox} from "element-plus";
+
+const emit = defineEmits(['update:show', 'refresh'])
+const {proxy} = getCurrentInstance()
+
+const props = defineProps({
+  show: {default: false},
+  transfer: <any>{}
+})
+const state: any = reactive({
+  loading: false,
+  form: {
+    type: 'String',
+  },
+})
+const optionsType = [
+  {type: 'String', label: '文本'},
+  {type: 'Textarea', label: '段落'},
+  {type: 'Number', label: '数字'},
+  // {type: 'Select', label: '下拉选项'},
+]
+const titleCpt = computed(() => {
+  let t = '变量'
+  switch (props.transfer.mode) {
+    case 'add': t = '新增' + t
+      break
+    case 'edit': t = '编辑' + t
+      break
+  }
+  return t
+})
+const ref_form = ref()
+watch(() => props.show, (n) => {
+  if (n) {
+    state.form = props.transfer.row || {type: 'String'}
+    console.log(state.form)
+    nextTick(() => {
+      ref_form.value.reset()
+    })
+  }
+})
+const onSubmit = () => {
+  ref_form.value.submit().then(() => {
+    emit('update:show', false)
+    const res: any = {
+      label: state.form.label,
+      key: state.form.key,
+      type: state.form.type,
+    }
+    if (state.form.type === 'String' ||  state.form.type === 'Textarea') {
+      res.length = state.form.length
+    }
+    emit('refresh', res)
+  }).catch((e) => {
+    ElMessage({
+      message: e[0].message,
+      grouping: true,
+      type: 'warning',
+    })
+  })
+}
+</script>
+
+<style lang="scss" scoped>
+.vars-detail {
+  padding: 10px 20px 0;
+  .vars-detail-types {
+    display: grid;
+    grid-template-columns: repeat(3, 1fr);
+    gap: 10px;
+    >div {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      border: 1px solid #ccc;
+      border-radius: 8px;
+      padding: 20px;
+      &.active, &:hover {
+        border-color: var(--czr-main-color);
+        color: var(--czr-main-color);
+      }
+    }
+  }
+  .vars-detail-form {
+    margin-top: 20px;
+  }
+}
+</style>

+ 35 - 0
src/views/workflow/instance/component/vars/vars-item.vue

@@ -0,0 +1,35 @@
+<template>
+  <div class="vars-item" @mouseenter="state.hover = true" @mouseleave="state.hover = false">
+    <SvgIcon name="vars" color="#155aef" size="14"/>
+    <span>{{item.key}}</span>
+    <span v-if="item.label" class="opacity-65"> · {{item.label}}</span>
+    <span class="ml-auto">{{item.type}}</span>
+    <template v-if="state.hover">
+      <SvgIcon v-if="edit" name="czr_edit" color="#155aef" size="12" class="__hover" @click="$emit('onEdit')"/>
+      <SvgIcon v-if="del" name="czr_del" color="#155aef" size="14" class="__hover" @click="$emit('onDel')"/>
+    </template>
+  </div>
+</template>
+
+<script setup lang="ts">
+import {getCurrentInstance, reactive, ref} from "vue";
+
+const emits = defineEmits([])
+const props = defineProps({
+  item: <any>{},
+  hover: false,
+  edit: {default: false},
+  del: {default: false},
+})
+const {proxy}: any = getCurrentInstance()
+const state: any = reactive({})
+</script>
+
+<style lang="scss" scoped>
+.vars-item {
+  display: flex;
+  align-items: center;
+  font-size: 12px;
+  gap: 6px;
+}
+</style>

+ 7 - 0
src/views/workflow/instance/root/default.ts

@@ -0,0 +1,7 @@
+const nodeDefault = {
+  defaultValue: {
+    inVars: []
+  }
+}
+
+export default nodeDefault

+ 68 - 1
src/views/workflow/instance/root/panel/index.vue

@@ -1,11 +1,38 @@
 <template>
   <div class="root-panel" v-if="state.nodeData">
-    <el-input v-model="state.nodeData.p1"/>
+    <div class="flex justify-between items-center mb-2">
+      <div class="text-sm">输入字段</div>
+      <SvgIcon name="czr_add" size="12" class="__hover" @click="onAddVars"/>
+    </div>
+    <div class="vars">
+      <template v-for="(item, index) in state.nodeData.inVars">
+        <div class="item">
+          <varsItem
+            :item="item"
+            :edit="true"
+            :del="true"
+            @onEdit="onEditVars(item, index)"
+            @onDel="onDelVars(item, index)"
+          />
+        </div>
+      </template>
+    </div>
+    <div class="w-full h-[1px] bg-[#1018281f] my-2"/>
+    <div class="vars">
+      <template v-for="item in state.nodeData.sysVars">
+        <div class="item">
+          <varsItem :item="item"/>
+        </div>
+      </template>
+    </div>
   </div>
+  <varsDetail v-model:show="state.vars.show" :transfer="state.vars.transfer" @refresh="val => setVars(val)"/>
 </template>
 
 <script setup lang="ts">
 import {getCurrentInstance, reactive, ref, watch} from "vue";
+import varsItem from "@/views/workflow/instance/component/vars/vars-item.vue";
+import varsDetail from "@/views/workflow/instance/component/vars/vars-detail.vue";
 
 const emits = defineEmits([])
 const props = defineProps({
@@ -14,13 +41,53 @@ const props = defineProps({
 const {proxy}: any = getCurrentInstance()
 const state: any = reactive({
   nodeData: null,
+  vars: {
+    show: false,
+    transfer: {}
+  }
 })
 watch(() => props.node, (n) => {
   if (n) {
     state.nodeData = n.data
   }
 }, {immediate: true})
+const onAddVars = () => {
+  state.vars.transfer = {
+    mode: 'add'
+  }
+  state.vars.show = true
+}
+const onEditVars = (row, index) => {
+  state.vars.transfer = {
+    mode: 'edit',
+    row: JSON.parse(JSON.stringify(row)),
+    index: index,
+  }
+  state.vars.show = true
+}
+const onDelVars = (row, index) => {
+  state.nodeData.inVars.splice(index, 1)
+}
+const setVars = (val) => {
+  if (state.vars.transfer.mode === 'add') {
+    state.nodeData.inVars.push(val)
+  } else {
+    state.nodeData.inVars[state.vars.transfer.index] = val
+  }
+}
 </script>
 
 <style lang="scss" scoped>
+.root-panel {
+  .vars {
+    display: flex;
+    flex-direction: column;
+    gap: 6px;
+    .item {
+      padding: 6px 10px;
+      border: 1px solid rgb(234 236 240);
+      border-radius: 8px;
+    }
+  }
+}
 </style>

+ 29 - 3
src/views/workflow/mockJson.ts

@@ -1,14 +1,36 @@
-export const data = {
+export const data1 = {
   "nodes": [
     {
       "id": "3ba412bb-3772-4f61-85de-095f4017858c",
       "x": -370,
       "y": -120,
       "data": {
+        "id": "3ba412bb-3772-4f61-85de-095f4017858c",
         "title": "开始",
         "type": "root",
-        "p1": "开始的参数1",
-        "id": "3ba412bb-3772-4f61-85de-095f4017858c"
+        "inVars": [
+          {
+            "label": "参数1",
+            "key": "key1",
+            "type": "String",
+            "length": "10"
+          },
+          {
+            "label": "参数2",
+            "key": "key2",
+            "type": "Number"
+          }
+        ],
+        "sysVars": [
+          {
+            "key": "sys.query",
+            "type": "String"
+          },
+          {
+            "key": "sys.user_id",
+            "type": "String"
+          }
+        ]
       }
     },
     {
@@ -212,3 +234,7 @@ export const data = {
     }
   ]
 }
+export const data2 = {
+  "nodes": [],
+  "edges": []
+}