CzRger 4 місяців тому
батько
коміт
4b126c4af4

+ 29 - 3
src/stores/modules/workflow.ts

@@ -1,5 +1,4 @@
 import {defineStore} from "pinia";
-import {getInVars} from "@/views/workflow/instance/funcs";
 
 export const useWorkflowStore = defineStore('workflow', {
   state: () => ({
@@ -7,7 +6,11 @@ export const useWorkflowStore = defineStore('workflow', {
     panel: {
       node: <any>null,
       show: false
-    }
+    },
+    systemVars: [
+      {label: '查询内容', key: 'sys.query', type: 'String'},
+      {label: '用户ID', key: 'sys.user_id', type: 'String'},
+    ]
   }),
   getters: {
   },
@@ -30,6 +33,29 @@ export const useWorkflowStore = defineStore('workflow', {
         node.portProp(portId, ['args', 'dy'], dom.offsetTop + 17)
       }
     },
-    // getInVars(node) => getInVars(node, this.graph)
+    getInVars(node) {
+      const pathNodes = this.graph.getPredecessors(node).reverse()
+      const options: any = []
+      pathNodes.forEach(n => {
+        const group = {
+          label: n.data.title,
+          key: n.data.id,
+          options: [
+            ...(n.data.outVars?.map(v => {
+              v.nodeId = n.data.id
+              v.nodeTitle = n.data.title
+              return v
+            }) || []),
+            ...(n.data.sysVars?.map(v => {
+              v.nodeId = n.data.id
+              v.nodeTitle = n.data.title
+              return v
+            }) || []),
+          ]
+        }
+        options.push(group)
+      })
+      return options
+    }
   },
 })

+ 8 - 0
src/style/czr.scss

@@ -10,6 +10,14 @@
   }
 }
 
+.__hover-bg {
+  &:hover {
+    opacity: 0.75;
+    cursor: pointer;
+    background-color: #c8ceda33;
+  }
+}
+
 .__text-ellipsis {
   white-space: nowrap;
   overflow: hidden;

+ 2 - 1
src/views/workflow/chart/context-menu-tool.vue

@@ -26,11 +26,12 @@ const onDel = () => {
 </script>
 
 <style lang="scss" scoped>
+@import "@/views/workflow/instance/component/style";
 .context-menu-tool {
   min-width: 100px;
   padding: 10px;
   background-color: #ffffff;
-  border: 1px solid rgba(0, 0, 0, 0.1);
+  border: $borderStyle;
   border-radius: 10px;
   box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
   .del {

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

@@ -82,13 +82,15 @@ const onAddNode = (type) => {
 </script>
 
 <style lang="scss" scoped>
+@import "@/views/workflow/instance/component/style";
+
 .port {
   width: 100%;
   height: 100%;
   >div {
     width: 100%;
     height: 100%;
-    border: 1px solid #8f8f8f;
+    border: $borderStyle;
     background-color: #ffffff;
     border-radius: 50%;
   }

+ 3 - 2
src/views/workflow/chart/node-index.vue

@@ -36,10 +36,11 @@ onMounted(() => {
 </script>
 
 <style lang="scss" scoped>
+@import "@/views/workflow/instance/component/style";
+
 .node {
   z-index: 10;
-  border-style: solid;
-  border-color: rgba(0, 0, 0, 0.1);
+  border: $borderStyle;
   border-radius: 10px;
   background-color: #ffffff;
   position: absolute;

+ 8 - 2
src/views/workflow/chart/panel-index.vue

@@ -9,6 +9,9 @@
         <template v-if="nodeDataCpt.type === NodeType.Root">
           <rootPanel :node="WorkflowStore.panel.node"/>
         </template>
+        <template v-if="nodeDataCpt.type === NodeType.Answer">
+          <answerPanel :node="WorkflowStore.panel.node"/>
+        </template>
         <template v-if="nodeDataCpt.type === NodeType.IfElse">
           <ifElsePanel :node="WorkflowStore.panel.node"/>
         </template>
@@ -20,6 +23,7 @@
 <script setup lang="ts">
 import {computed, getCurrentInstance, reactive, ref} from "vue";
 import rootPanel from '../instance/root/panel/index.vue'
+import answerPanel from '../instance/answer/panel/index.vue'
 import ifElsePanel from '../instance/if-else/panel/index.vue'
 import {useWorkflowStore} from "@/stores";
 import {NodeType} from "@/views/workflow/types";
@@ -35,6 +39,8 @@ const nodeDataCpt = computed(() => WorkflowStore.panel.node?.data || {})
 </script>
 
 <style lang="scss" scoped>
+@import "@/views/workflow/instance/component/style";
+
 .panel {
   position: fixed;
   top: 0;
@@ -49,13 +55,13 @@ const nodeDataCpt = computed(() => WorkflowStore.panel.node?.data || {})
   border-radius: 10px 0 0 10px;
   .panel-header {
     padding: 16px;
-    border-bottom: 1px solid #eee;
+    border-bottom: $borderStyle;
     display: flex;
     justify-content: space-between;
     align-items: center;
   }
   .panel-content {
-    padding: 16px;
+    padding: 0 16px 16px;
     flex: 1;
     overflow-y: auto;
   }

+ 3 - 6
src/views/workflow/handle.ts

@@ -2,12 +2,9 @@ import {Markup} from "@antv/x6";
 import {merge} from "lodash";
 import {lineStyle, portStyle} from "@/views/workflow/config";
 import { v4 } from "uuid";
+import {useWorkflowStore} from "@/stores";
 
-const systemVars = [
-  {label: '查询内容', key: 'sys.query', type: 'String'},
-  {label: '用户ID', key: 'sys.user_id', type: 'String'},
-]
-
+const WorkflowStore = useWorkflowStore()
 export const handleNode = (no) => {
   const id = v4()
   if (!no.id) {
@@ -42,7 +39,7 @@ export const handleNode = (no) => {
   }
   if (node.data.type === 'root') {
     node.data.sysVars = [
-      ...systemVars
+      ...WorkflowStore.systemVars
     ]
   } else {
     node.ports.items.push({

+ 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 {data1, data2} from './mockJson'
+import {data0, data1, data2} from './mockJson'
 const TeleportContainer = getTeleport()
 
 const emits = defineEmits([])
@@ -55,7 +55,7 @@ const getJsonData = () => {
   console.log(res)
 }
 const initData = () => {
-  state.workflowData = data2
+  state.workflowData = data1
 }
 onMounted(() => {
   initData()

+ 0 - 1
src/views/workflow/instance/answer/default.ts

@@ -1,7 +1,6 @@
 const nodeDefault = {
   defaultValue: {
     inVars: [],
-    outVars: [],
   }
 }
 

+ 8 - 64
src/views/workflow/instance/answer/panel/index.vue

@@ -1,39 +1,18 @@
 <template>
-  <div class="root-panel" v-if="state.nodeData">
-    <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.outVars">
-        <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 class="panel-block" v-if="state.nodeData">
+    <div class="_p-title" style="margin-top: 0">
+      <div class="text-sm">可用变量</div>
     </div>
+    <varsSelect :node="props.node"/>
   </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";
+import varsSelect from "@/views/workflow/instance/component/vars/vars-select.vue";
+import {useWorkflowStore} from "@/stores";
 
+const WorkflowStore = useWorkflowStore()
 const emits = defineEmits([])
 const props = defineProps({
   node: <any>{}
@@ -51,43 +30,8 @@ watch(() => props.node, (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.outVars.splice(index, 1)
-}
-const setVars = (val) => {
-  if (state.vars.transfer.mode === 'add') {
-    state.nodeData.outVars.push(val)
-  } else {
-    state.nodeData.outVars[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;
-    }
-  }
-}
+@import "@/views/workflow/instance/component/style";
 </style>

+ 8 - 0
src/views/workflow/instance/component/style.scss

@@ -0,0 +1,8 @@
+$borderStyle: 1px solid rgba(0, 0, 0, 0.1);
+
+._p-title {
+  margin: 10px 0;
+  display: flex;
+  align-items: center;
+  justify-content:  space-between;
+}

+ 3 - 1
src/views/workflow/instance/component/vars/vars-detail.vue

@@ -107,6 +107,8 @@ const onSubmit = () => {
 </script>
 
 <style lang="scss" scoped>
+@import "@/views/workflow/instance/component/style";
+
 .vars-detail {
   padding: 10px 20px 0;
   .vars-detail-types {
@@ -117,7 +119,7 @@ const onSubmit = () => {
       display: flex;
       align-items: center;
       justify-content: center;
-      border: 1px solid #ccc;
+      border: $borderStyle;
       border-radius: 8px;
       padding: 20px;
       &.active, &:hover {

+ 4 - 3
src/views/workflow/instance/component/vars/vars-item.vue

@@ -3,7 +3,7 @@
     <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>
+    <span class="ml-auto opacity-65">{{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')"/>
@@ -17,12 +17,13 @@ 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({})
+const state: any = reactive({
+  hover: false,
+})
 </script>
 
 <style lang="scss" scoped>

+ 101 - 0
src/views/workflow/instance/component/vars/vars-select.vue

@@ -0,0 +1,101 @@
+<template>
+  <div class="vars-select">
+    <el-popover
+      placement="bottom"
+      trigger="click"
+      :width="400"
+      :popper-style="{
+        padding: 0
+      }"
+    >
+      <template #reference>
+        <div class="vars-display __hover">
+          <varsValue :vars="state.vars"/>
+        </div>
+      </template>
+      <div class="vars-select-block">
+        <div class="filter">
+          <el-input v-model="state.text" :prefix-icon="Search" placeholder="搜索变量" clearable/>
+        </div>
+        <div class="list">
+          <template v-for="item in optionsCpt">
+            <div class="list-group">
+              <div class="px-2 text-sm mb-1 text-gray-400">{{item.label}}</div>
+              <template v-for="son in item.options">
+                <div class="__hover-bg px-2 py-1" @click="state.vars = son">
+                  <varsItem :item="son"/>
+                </div>
+              </template>
+            </div>
+          </template>
+        </div>
+      </div>
+    </el-popover>
+  </div>
+</template>
+
+<script setup lang="ts">
+import {computed, getCurrentInstance, reactive, ref, watch} from "vue";
+import {useWorkflowStore} from "@/stores";
+import { Search } from '@element-plus/icons-vue'
+import varsItem from './vars-item.vue'
+import varsValue from './vars-value.vue'
+
+const WorkflowStore = useWorkflowStore()
+const emits = defineEmits([])
+const props = defineProps({
+  node: {}
+})
+const {proxy}: any = getCurrentInstance()
+const state: any = reactive({
+  vars: null,
+  options: [],
+  text: ''
+})
+watch(() => props.node, (n) => {
+  if (n) {
+    state.options = WorkflowStore.getInVars(n)
+  }
+}, {immediate: true})
+const optionsCpt = computed(() => {
+  if (!state.text) {
+    return state.options
+  }
+  return state.options.map(v => {
+    const obj = {...v}
+    obj.options = obj.options.filter(s => s.label.includes(state.text) || s.key.includes(state.text))
+    return obj
+  }).filter(v => v.options.length > 0)
+})
+</script>
+
+<style lang="scss" scoped>
+@import "@/views/workflow/instance/component/style";
+
+.vars-display {
+  border: $borderStyle;
+  width: 100%;
+  padding: 0 10px;
+  border-radius: 4px;
+  font-size: 12px;
+  height: 32px;
+  display: flex;
+  align-items: center;
+}
+.vars-select-block {
+  .filter {
+    padding: 10px;
+    border-bottom: $borderStyle;
+  }
+  .list {
+    padding: 4px;
+    display: flex;
+    flex-direction: column;
+    gap: 6px;
+    .list-group {
+      display: flex;
+      flex-direction: column;
+    }
+  }
+}
+</style>

+ 19 - 0
src/views/workflow/instance/component/vars/vars-value.vue

@@ -0,0 +1,19 @@
+<template>
+  <div class="vars-value" v-if="vars">
+    {{vars.nodeTitle}} / <span class="text-[#155aef]">{{vars.key}}</span> · <span class="opacity-65">{{vars.label}} {{vars.type}}</span>
+  </div>
+</template>
+
+<script setup lang="ts">
+import {getCurrentInstance, reactive, ref} from "vue";
+
+const emits = defineEmits([])
+const props = defineProps({
+  vars: <any>{}
+})
+const {proxy}: any = getCurrentInstance()
+const state: any = reactive({})
+</script>
+
+<style lang="scss" scoped>
+</style>

+ 0 - 3
src/views/workflow/instance/funcs.ts

@@ -1,3 +0,0 @@
-export const getInVars = (node, graph) => {
-
-}

+ 22 - 10
src/views/workflow/instance/if-else/panel/index.vue

@@ -1,15 +1,15 @@
 <template>
-  <div class="root-panel" v-if="state.nodeData">
-    <template v-for="(port, index) in state.nodeData.ports">
-      <div v-if="index < state.nodeData.ports.length - 1">
-        <div>
-          CASE {{index + 1}}
+  <div class="panel-block" v-if="state.nodeData">
+    <div class="case-list">
+      <template v-for="(port, index) in state.nodeData.ports">
+        <div v-if="index < state.nodeData.ports.length - 1" class="case-item">
+          <div class="cast-title">
+            <div>{{index === 0 ? 'IF' : 'ELSE IF'}}</div>
+            <div>CASR {{index + 1}}</div>
+          </div>
         </div>
-        <div>
-          <el-input v-model="port.data.p1"/>
-        </div>
-      </div>
-    </template>
+      </template>
+    </div>
     <el-button type="primary" @click="onAddPort">添加条件分支</el-button>
   </div>
 </template>
@@ -60,4 +60,16 @@ const onAddPort = () => {
 </script>
 
 <style lang="scss" scoped>
+@import "@/views/workflow/instance/component/style";
+
+.panel-block {
+  .case-list {
+    display: flex;
+    flex-direction: column;
+    .case-item {
+      border-bottom: $borderStyle;
+      padding: 10px 0;
+    }
+  }
+}
 </style>

+ 6 - 4
src/views/workflow/instance/root/panel/index.vue

@@ -1,6 +1,6 @@
 <template>
-  <div class="root-panel" v-if="state.nodeData">
-    <div class="flex justify-between items-center mb-2">
+  <div class="panel-block" v-if="state.nodeData">
+    <div class="_p-title">
       <div class="text-sm">输入字段</div>
       <SvgIcon name="czr_add" size="12" class="__hover" @click="onAddVars"/>
     </div>
@@ -78,14 +78,16 @@ const setVars = (val) => {
 </script>
 
 <style lang="scss" scoped>
-.root-panel {
+@import "@/views/workflow/instance/component/style";
+
+.panel-block {
   .vars {
     display: flex;
     flex-direction: column;
     gap: 6px;
     .item {
       padding: 6px 10px;
-      border: 1px solid rgb(234 236 240);
+      border: $borderStyle;
       border-radius: 8px;
     }
   }

+ 4 - 0
src/views/workflow/mockJson.ts

@@ -1,3 +1,7 @@
+export const data0 = {
+  "nodes": [],
+  "edges": []
+}
 export const data1 = {
   "nodes": [
     {