CzRger 3 tygodni temu
rodzic
commit
590c4ca960

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

@@ -73,7 +73,7 @@ const props = defineProps({
 <style lang="scss" scoped>
 .czr-button {
   width: fit-content;
-  height: 2.25rem;
+  height: 2rem;
   background: #ffffff;
   border-radius: 0.25rem;
   display: flex;
@@ -100,13 +100,11 @@ const props = defineProps({
     border-color: var(--czr-main-color);
   }
   &.primary {
-    height: 2rem;
     background: var(--czr-main-color);
     border-color: var(--czr-main-color);
     color: #ffffff;
   }
   &.normal {
-    height: 2rem;
     background: #ffffff;
     border-color: var(--czr-main-color);
     color: var(--czr-main-color);

+ 54 - 3
src/views/process/chart/context-menu-tool.vue

@@ -1,22 +1,30 @@
 <template>
   <div
-    class="min-w-25 rounded-sm bg-[#ffffff] px-2.5 py-1.5 text-sm text-[#606266] shadow"
+    class="flex min-w-25 flex-col gap-2 rounded-sm bg-[#ffffff] px-2.5 py-2 text-sm text-[#606266] shadow"
     :context-menu-tools="true"
   >
     <div class="__hover" @click="onAdd">添加审批节点</div>
+    <div
+      class="__hover text-[var(--czr-error-color)]"
+      @click="onDel"
+      v-if="isDelCpt"
+    >
+      删除节点
+    </div>
   </div>
 </template>
 
 <script setup lang="ts">
-import { getCurrentInstance, reactive } from 'vue'
+import { computed, getCurrentInstance, reactive } from 'vue'
 import { GraphHistoryStep } from '@/views/workflow/types'
 import { getNodeDefault } from '@/views/process/config'
 import { NodeType } from '@/views/process/types'
 import { handleEdge, handleNode } from '@/views/process/handle'
 import { v4 } from 'uuid'
-import { useProcessStore } from '@/stores'
+import { useDialogStore, useProcessStore } from '@/stores'
 
 const ProcessStore = useProcessStore()
+const DialogStore = useDialogStore()
 const emit = defineEmits([])
 const props = defineProps({
   data: <any>{},
@@ -25,6 +33,15 @@ const props = defineProps({
 const { proxy }: any = getCurrentInstance()
 
 const state: any = reactive({})
+const isDelCpt = computed(() => {
+  const cell = ProcessStore.graph.getCellById(props.data.id)
+  if (cell.isNode()) {
+    if (![NodeType.Start, NodeType.End].includes(cell.data.workflowData.type)) {
+      return true
+    }
+  }
+  return false
+})
 const onAdd = () => {
   ProcessStore.graph.startBatch(GraphHistoryStep.NodeAdd)
   const cell = ProcessStore.graph.getCellById(props.data.id)
@@ -87,6 +104,40 @@ const onAdd = () => {
   }, 100)
   props.onClose(null, true)
 }
+const onDel = () => {
+  DialogStore.confirm({
+    content: `请确认是否删除节点?`,
+    onSubmit: () => {
+      ProcessStore.graph.startBatch(GraphHistoryStep.NodeDel)
+      const currentNode = ProcessStore.graph.getCellById(props.data.id)
+      const lastNode = ProcessStore.graph.getCellById(
+        currentNode.data.workflowData.edgeSource[0],
+      )
+      const nextNode = ProcessStore.graph.getCellById(
+        currentNode.data.workflowData.edgeTarget[0],
+      )
+      lastNode.data.workflowData.edgeTarget = [nextNode.id]
+      nextNode.data.workflowData.edgeSource = [lastNode.id]
+      const lastEdge = ProcessStore.graph.getIncomingEdges(currentNode)[0]
+      const nextEdge = ProcessStore.graph.getOutgoingEdges(currentNode)[0]
+      ProcessStore.graph.removeEdge(lastEdge)
+      ProcessStore.graph.removeEdge(nextEdge)
+      ProcessStore.graph.removeNode(currentNode)
+      ProcessStore.graph.addEdge(
+        handleEdge({
+          id: v4(),
+          source: lastNode.id,
+          target: nextNode.id,
+        }),
+      )
+      setTimeout(() => {
+        ProcessStore.onAutoFix()
+        ProcessStore.graph.stopBatch(GraphHistoryStep.NodeDel)
+      }, 100)
+      props.onClose(null, true)
+    },
+  })
+}
 </script>
 
 <style lang="scss" scoped></style>

+ 3 - 4
src/views/process/chart/index.vue

@@ -341,11 +341,10 @@ const initWatch = () => {
     })
   })
   state.graph.on('node:click', ({ node }) => {
-    if (['process-node-start', 'process-node-end'].includes(node.shape)) {
+    if ([NodeType.Start, NodeType.End].includes(node.data.workflowData.type)) {
       return
     }
     setTimeout(() => {
-      console.log(node)
       ProcessStore.nodePanelShow(node)
       node.attr('select', true)
       const connectEdges = state.graph.getConnectedEdges(node.id)
@@ -399,7 +398,7 @@ const initWatch = () => {
   })
   state.graph.on('translate', ({ sx, sy, ox, oy }) => {
     if (state.autoSaveInit) {
-      ProcessStore.autoSave()
+      // ProcessStore.autoSave()
     }
   })
   state.graph.on('history:change', ({ cmds, options }) => {
@@ -427,7 +426,7 @@ const initWatch = () => {
         state.history.current = 0
       }
       if (state.autoSaveInit) {
-        ProcessStore.autoSave()
+        // ProcessStore.autoSave()
       } else {
         state.autoSaveInit = true
       }

+ 6 - 1
src/views/process/chart/nodes/approval.vue

@@ -5,9 +5,10 @@
     :class="{
       active: state.active,
       panel: ProcessStore.panel.node?.id === state.node?.id,
+      valid: !state.nodeData?.valid,
     }"
   >
-    {{ state.nodeData?.title }}
+    {{ state.nodeData?.name || state.nodeData?.title }}
   </div>
 </template>
 
@@ -62,5 +63,9 @@ onMounted(() => {
     border-color: rgba(var(--czr-main-color-rgb), 1);
     cursor: pointer;
   }
+  &.valid {
+    border-color: rgba(var(--czr-error-color-rgb), 1);
+    cursor: pointer;
+  }
 }
 </style>

+ 10 - 19
src/views/process/chart/panel-index.vue

@@ -4,8 +4,12 @@
       class="z-1000 flex h-full w-[400px] flex-col rounded-lg bg-white shadow"
       v-if="ProcessStore.panel.show"
     >
-      <div class="__czr-title_2">
-        {{ state.nodeData.title }}
+      <div
+        class="flex h-11 items-center bg-[url('@/assets/images/global/head-bg-1.png')] bg-[length:100%_100%] bg-no-repeat px-4"
+      >
+        <div class="__czr-title_2">
+          {{ state.nodeData.title }}
+        </div>
         <div
           class="__hover ml-auto text-xl"
           @click="ProcessStore.nodePanelClose()"
@@ -13,24 +17,11 @@
           ×
         </div>
       </div>
-      <div class="panel-sub p-1.5">
-        <CzrFormColumn
-          :span="24"
-          label-width="0px"
-          v-model:param="state.nodeData.desc"
-          :transparent="true"
-          placeholder="添加描述..."
-          :max-length="20"
-          :clearable="false"
-        />
-      </div>
-      <div class="overflow-y-auto px-4 pb-4">
-        <template v-for="(value, key) in nodeSources">
-          <template v-if="nodeDataCpt.type === key">
-            <component :is="value.panelCom" :node="ProcessStore.panel.node" />
-          </template>
+      <template v-for="(value, key) in nodeSources">
+        <template v-if="nodeDataCpt.type === key && value.panelCom">
+          <component :is="value.panelCom" :node="ProcessStore.panel.node" />
         </template>
-      </div>
+      </template>
     </div>
   </transition>
 </template>

+ 3 - 0
src/views/process/config.ts

@@ -29,6 +29,7 @@ export const nodeSources = {
           title: NodeTypeObj[NodeType.Start].title,
           edgeSource: null,
           edgeTarget: [],
+          valid: true,
         },
       },
   },
@@ -42,6 +43,7 @@ export const nodeSources = {
           title: NodeTypeObj[NodeType.End].title,
           edgeSource: [],
           edgeTarget: null,
+          valid: true,
         },
       },
   },
@@ -55,6 +57,7 @@ export const nodeSources = {
           title: NodeTypeObj[NodeType.Approval].title,
           edgeSource: [],
           edgeTarget: [],
+          valid: false,
         },
       },
     panelCom: defineAsyncComponent(

+ 61 - 39
src/views/process/index.vue

@@ -5,7 +5,10 @@
       <workflowChart :ID="ID" :data="state.workflowData" ref="ref_workflow" />
       <div class="absolute top-4 left-4 flex items-center gap-4">
         <CzrButton type="primary" title="保存" @click="onSave" />
-        <div class="flex items-center text-[var(--czr-error-color)]">
+        <div
+          class="flex items-center text-[var(--czr-error-color)]"
+          v-if="state.isEdit"
+        >
           <SvgIcon name="czr_tip" color="var(--czr-error-color)" class="mr-1" />
           有未保存的修改
         </div>
@@ -32,11 +35,12 @@ import {
 import workflowChart from './chart/index.vue'
 import workflowPanel from './chart/panel-index.vue'
 import { getTeleport } from '@antv/x6-vue-shape'
-import { useAppStore, useDictionaryStore, useProcessStore } from '@/stores'
+import { useAppStore, useDialogStore, useProcessStore } from '@/stores'
 import { debounce } from 'lodash'
-import { ElLoading } from 'element-plus'
+import { ElLoading, ElMessage } from 'element-plus'
+import { appApiKeysAdd, appApiKeysEdit } from '@/api/modules/app'
 
-const DictionaryStore = useDictionaryStore()
+const DialogStore = useDialogStore()
 const AppStore = useAppStore()
 const TeleportContainer = getTeleport()
 const ProcessStore = useProcessStore()
@@ -47,16 +51,13 @@ const props = defineProps({
 const { proxy }: any = getCurrentInstance()
 const state: any = reactive({
   workflowData: null,
+  isEdit: false,
 })
 const ref_workflow = ref()
-
-const autoSave = debounce(() => {
-  onSave()
-}, 5000)
 watch(
   () => ProcessStore.autoSaveFlag,
   () => {
-    autoSave()
+    state.isEdit = true
   },
 )
 const onSave = () => {
@@ -91,11 +92,22 @@ const onSave = () => {
       res.graph.nodes.push(node)
     }
   })
+  const isValid = res.graph.nodes.every((v) => v.data.valid)
+  if (!isValid) {
+    DialogStore.confirm({
+      props: {
+        showCancel: false,
+      },
+      content: `检测到未校验成功的节点,请重新检查节点配置!`,
+    })
+    return
+  }
   const loading = ElLoading.service({
     text: '保存中……',
     background: 'rgba(0, 0,0, 0.3)',
   })
   console.log(res)
+  state.isEdit = false
   loading.close()
 }
 const initData = () => {
@@ -107,91 +119,101 @@ const initData = () => {
     graph: {
       nodes: [
         {
-          id: '56ad6c45-c8b5-4add-b39a-48ecf4c11b3d',
+          id: '40fc7fd8-9adc-4446-a1bb-a0e8546cbdce',
           type: 'start',
           position: {
             x: 23,
             y: 35,
           },
           data: {
-            id: '56ad6c45-c8b5-4add-b39a-48ecf4c11b3d',
+            id: '40fc7fd8-9adc-4446-a1bb-a0e8546cbdce',
             title: '发起',
             edgeSource: null,
-            edgeTarget: ['c9102b83-cda6-495f-b8c0-4d3a85fa26db'],
+            edgeTarget: ['3f1ebbdd-ba81-47e5-9e28-47106774cabb'],
+            valid: true,
             type: 'start',
           },
         },
         {
-          id: 'cbec7e8f-c094-440a-bbc5-714c032196eb',
+          id: '937362a7-7122-4b1a-a7b2-02c9a1a155fd',
           type: 'end',
           position: {
             x: 23,
             y: 477,
           },
           data: {
-            id: 'cbec7e8f-c094-440a-bbc5-714c032196eb',
+            id: '937362a7-7122-4b1a-a7b2-02c9a1a155fd',
             title: '结束',
-            edgeSource: ['3f2a36d7-94f6-404c-bcc1-c3392d0ee626'],
+            edgeSource: ['0bf28835-8eb2-4596-bab4-ec5bb2314390'],
             edgeTarget: null,
+            valid: true,
             type: 'end',
           },
         },
         {
-          id: '3f2a36d7-94f6-404c-bcc1-c3392d0ee626',
+          id: '3f1ebbdd-ba81-47e5-9e28-47106774cabb',
           type: 'approval',
           position: {
-            x: 260,
-            y: 350,
+            x: -200,
+            y: 234,
           },
           data: {
-            id: '3f2a36d7-94f6-404c-bcc1-c3392d0ee626',
+            id: '3f1ebbdd-ba81-47e5-9e28-47106774cabb',
             title: '审批节点',
-            edgeSource: ['c9102b83-cda6-495f-b8c0-4d3a85fa26db'],
-            edgeTarget: ['cbec7e8f-c094-440a-bbc5-714c032196eb'],
+            edgeSource: ['40fc7fd8-9adc-4446-a1bb-a0e8546cbdce'],
+            edgeTarget: ['0bf28835-8eb2-4596-bab4-ec5bb2314390'],
+            valid: true,
             type: 'approval',
+            name: '55555',
+            linkAccount: 'role',
+            roles: ['1968135147337920512'],
           },
         },
         {
-          id: 'c9102b83-cda6-495f-b8c0-4d3a85fa26db',
+          id: '0bf28835-8eb2-4596-bab4-ec5bb2314390',
           type: 'approval',
           position: {
-            x: -240,
-            y: 210,
+            x: 170,
+            y: 270,
           },
           data: {
-            id: 'c9102b83-cda6-495f-b8c0-4d3a85fa26db',
+            id: '0bf28835-8eb2-4596-bab4-ec5bb2314390',
             title: '审批节点',
-            edgeSource: ['56ad6c45-c8b5-4add-b39a-48ecf4c11b3d'],
-            edgeTarget: ['3f2a36d7-94f6-404c-bcc1-c3392d0ee626'],
+            edgeSource: ['3f1ebbdd-ba81-47e5-9e28-47106774cabb'],
+            edgeTarget: ['937362a7-7122-4b1a-a7b2-02c9a1a155fd'],
+            valid: true,
             type: 'approval',
+            name: 'kkfkkfkk',
+            linkAccount: 'account',
+            accounts: ['0', '1950731839749042176', '1977563023736336384'],
           },
         },
       ],
       edges: [
         {
-          id: '4baed89d-c158-4359-85f4-3bd12519e4b0',
-          target: 'cbec7e8f-c094-440a-bbc5-714c032196eb',
-          source: '3f2a36d7-94f6-404c-bcc1-c3392d0ee626',
+          id: 'ab1c1778-94d8-450c-b823-dd169a434fd2',
+          target: '3f1ebbdd-ba81-47e5-9e28-47106774cabb',
+          source: '40fc7fd8-9adc-4446-a1bb-a0e8546cbdce',
         },
         {
-          id: '931f583d-4cd0-4e2e-ae9b-06e780f07fa7',
-          target: 'c9102b83-cda6-495f-b8c0-4d3a85fa26db',
-          source: '56ad6c45-c8b5-4add-b39a-48ecf4c11b3d',
+          id: 'c766ffc2-fc71-4739-ba87-054fa164adcf',
+          target: '0bf28835-8eb2-4596-bab4-ec5bb2314390',
+          source: '3f1ebbdd-ba81-47e5-9e28-47106774cabb',
         },
         {
-          id: 'e1c401eb-a538-46f8-ac45-6c6deaf90338',
-          target: '3f2a36d7-94f6-404c-bcc1-c3392d0ee626',
-          source: 'c9102b83-cda6-495f-b8c0-4d3a85fa26db',
+          id: '09fef45f-f143-48a8-993f-6592ec313598',
+          target: '937362a7-7122-4b1a-a7b2-02c9a1a155fd',
+          source: '0bf28835-8eb2-4596-bab4-ec5bb2314390',
         },
       ],
       viewport: {
         zoom: 1,
-        x: 616,
-        y: 86,
+        x: 423,
+        y: 129,
       },
     },
   }
-  if (data.graph.viewport) {
+  if (data?.graph?.viewport) {
     state.workflowData = {
       viewport: data.graph.viewport,
       nodes: formatNodes(data.graph.nodes),

+ 134 - 6
src/views/process/instance/approval/panel/index.vue

@@ -1,11 +1,65 @@
 <template>
-  <div class="panel-block" v-if="state.nodeData">123123</div>
+  <div class="bm-form flex h-full flex-col" v-if="state.nodeData">
+    <CzrForm class="form" ref="ref_form">
+      <CzrFormColumn
+        label="节点名称"
+        :span="24"
+        v-model:param="state.nodeData.name"
+      />
+      <CzrFormColumn
+        required
+        label="关联账号"
+        :span="24"
+        v-model:param="state.nodeData.linkAccount"
+        link="radio"
+        :options="[
+          { label: '按角色选择', value: 'role' },
+          { label: '按账号选择', value: 'account' },
+        ]"
+      />
+      <template v-if="state.nodeData.linkAccount === 'role'">
+        <CzrFormColumn
+          required
+          label="角色"
+          :span="24"
+          v-model:param="state.nodeData.roles"
+          link="select"
+          :options="state.roleOptions"
+          :multiple="true"
+        />
+      </template>
+      <template v-else-if="state.nodeData.linkAccount === 'account'">
+        <CzrFormColumn
+          required
+          label="账号"
+          :span="24"
+          v-model:param="state.nodeData.accounts"
+          link="select"
+          :options="state.accountOptions"
+          :multiple="true"
+        />
+      </template>
+    </CzrForm>
+    <div class="mt-auto flex justify-center gap-2">
+      <CzrButton title="保存" type="primary" @click="onSubmit" />
+      <CzrButton type="del" @click="onDel" />
+    </div>
+  </div>
 </template>
 
 <script setup lang="ts">
-import { getCurrentInstance, reactive, ref, watch } from 'vue'
-import { useProcessStore } from '@/stores'
+import { getCurrentInstance, onMounted, reactive, ref, watch } from 'vue'
+import { useAppStore, useDialogStore, useProcessStore } from '@/stores'
+import { rolesPage } from '@/api/modules/center/role'
+import { userPage } from '@/api/modules/center/user'
+import { ElMessage } from 'element-plus'
+import { GraphHistoryStep } from '@/views/workflow/types'
+import { handleEdge } from '@/views/process/handle'
+import { v4 } from 'uuid'
 
+const AppStore = useAppStore()
+const DialogStore = useDialogStore()
+const ProcessStore = useProcessStore()
 const emit = defineEmits([])
 const props = defineProps({
   node: <any>{},
@@ -17,18 +71,92 @@ const state: any = reactive({
     show: false,
     transfer: {},
   },
+  roleOptions: null,
+  accountOptions: null,
 })
+const ref_form = ref()
 watch(
   () => props.node,
   (n) => {
     if (n) {
       state.nodeData = n.data.workflowData
+      state.nodeDataTemp = JSON.parse(JSON.stringify(state.nodeData))
     }
   },
   { immediate: true },
 )
+const onSubmit = () => {
+  ref_form.value
+    .submit()
+    .then(() => {
+      ElMessage.success(`审批节点保存成功!`)
+      state.nodeData.valid = true
+      ProcessStore.nodePanelClose()
+    })
+    .catch((e) => {
+      state.nodeData.valid = false
+      ElMessage({
+        message: e[0].message,
+        grouping: true,
+        type: 'warning',
+      })
+    })
+}
+const onDel = () => {
+  DialogStore.confirm({
+    content: `请确认是否删除节点?`,
+    onSubmit: () => {
+      ProcessStore.graph.startBatch(GraphHistoryStep.NodeDel)
+      const currentNode = ProcessStore.graph.getCellById(state.nodeData.id)
+      const lastNode = ProcessStore.graph.getCellById(
+        currentNode.data.workflowData.edgeSource[0],
+      )
+      const nextNode = ProcessStore.graph.getCellById(
+        currentNode.data.workflowData.edgeTarget[0],
+      )
+      lastNode.data.workflowData.edgeTarget = [nextNode.id]
+      nextNode.data.workflowData.edgeSource = [lastNode.id]
+      const lastEdge = ProcessStore.graph.getIncomingEdges(currentNode)[0]
+      const nextEdge = ProcessStore.graph.getOutgoingEdges(currentNode)[0]
+      ProcessStore.graph.removeEdge(lastEdge)
+      ProcessStore.graph.removeEdge(nextEdge)
+      ProcessStore.graph.removeNode(currentNode)
+      ProcessStore.graph.addEdge(
+        handleEdge({
+          id: v4(),
+          source: lastNode.id,
+          target: nextNode.id,
+        }),
+      )
+      setTimeout(() => {
+        ProcessStore.onAutoFix()
+        ProcessStore.graph.stopBatch(GraphHistoryStep.NodeDel)
+      }, 100)
+      ProcessStore.nodePanelClose()
+    },
+  })
+}
+onMounted(() => {
+  rolesPage({
+    page: 1,
+    size: 100000,
+    tenantId: AppStore.tenantInfo?.id,
+  }).then(({ data }: any) => {
+    state.roleOptions =
+      data.content?.map((v) => ({ label: v.roleName, value: v.id })) || []
+  })
+  userPage({
+    page: 1,
+    size: 100000,
+    tenantId: AppStore.tenantInfo?.id,
+  }).then(({ data }: any) => {
+    state.accountOptions =
+      data.content?.map((v) => ({
+        label: `${v.name}(${v.loginId})`,
+        value: v.id,
+      })) || []
+  })
+})
 </script>
 
-<style lang="scss" scoped>
-@use '@/views/workflow/instance/component/style';
-</style>
+<style lang="scss" scoped></style>

+ 49 - 0
src/views/process/instance/component/buttons/index.vue

@@ -0,0 +1,49 @@
+<template>
+  <div
+    class="__hover flex h-6 items-center justify-center gap-1 rounded-sm border-1 px-2 text-xs font-normal"
+    :class="`${styleCpt.borderColor} ${styleCpt.bgColor} ${styleCpt.textColor}`"
+  >
+    <SvgIcon v-if="icon" :name="icon" :color="styleCpt.textColor" size="10" />
+    {{ title }}
+  </div>
+</template>
+
+<script setup lang="ts">
+defineOptions({
+  name: 'workflowButton',
+})
+import { computed, reactive } from 'vue'
+
+const props = defineProps({
+  icon: {},
+  title: {},
+  type: { default: 'main' },
+})
+const state: any = reactive({})
+const styleCpt = computed(() => {
+  const obj = {
+    borderColor: '',
+    bgColor: '',
+    textColor: '',
+  }
+  switch (props.type) {
+    case 'main':
+      {
+        obj.borderColor = 'border-[var(--czr-main-color)]'
+        obj.bgColor = 'bg-[var(--czr-main-color)]'
+        obj.textColor = 'text-[#ffffff]'
+      }
+      break
+    case 'error':
+      {
+        obj.borderColor = 'border-[var(--czr-error-color)]'
+        obj.bgColor = 'bg-[var(--czr-error-color)]/10'
+        obj.textColor = 'text-[var(--czr-error-color)]'
+      }
+      break
+  }
+  return obj
+})
+</script>
+
+<style lang="scss" scoped></style>